#![allow(clippy::similar_names)]
#![forbid(unsafe_code)]
use super::vertex::Vertex;
use super::{
facet::{FacetError, FacetView},
tds::{SimplexKey, Tds, VertexKey},
traits::{DataDeserialize, DataSerialize},
util::{UuidValidationError, make_uuid, usize_to_u8, validate_uuid},
vertex::VertexValidationError,
};
use crate::core::collections::{
FastHashMap, FastHashSet, NeighborBuffer, PeriodicOffsetBuffer, SimplexVertexKeyBuffer,
SimplexVertexUuidBuffer,
};
use crate::geometry::matrix::StackMatrixDispatchError;
use crate::geometry::traits::coordinate::{CoordinateConversionError, CoordinateScalar};
use serde::{
Deserialize, Deserializer, Serialize,
de::{self, IgnoredAny, MapAccess, Visitor},
ser::SerializeStruct,
};
use std::{
cmp,
fmt::{self, Debug},
hash::{Hash, Hasher},
marker::PhantomData,
};
use thiserror::Error;
use uuid::Uuid;
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum SimplexValidationError {
#[error("Invalid vertex: {source}")]
InvalidVertex {
#[from]
source: VertexValidationError,
},
#[error("Invalid UUID: {source}")]
InvalidUuid {
#[from]
source: UuidValidationError,
},
#[error("Duplicate vertices: simplex contains non-unique vertices which is not allowed")]
DuplicateVertices,
#[error(
"Insufficient vertices: simplex has {actual} vertices; expected exactly {expected} for a {dimension}D simplex"
)]
InsufficientVertices {
actual: usize,
expected: usize,
dimension: usize,
},
#[error(
"Degenerate simplex: the vertices form a degenerate configuration that cannot reliably determine geometric properties"
)]
DegenerateSimplex,
#[error("Coordinate conversion error: {source}")]
CoordinateConversion {
#[from]
source: CoordinateConversionError,
},
#[error(
"Invalid neighbors length: got {actual}, expected {expected} (D+1) for a {dimension}D simplex"
)]
InvalidNeighborsLength {
actual: usize,
expected: usize,
dimension: usize,
},
#[error(
"Unassigned neighbor slot at facet {facet_index}; assigned neighbor buffers must contain only boundary or neighbor slots"
)]
UnassignedNeighborSlot {
facet_index: usize,
},
#[error("Periodic offset length mismatch: got {found}, expected {expected}")]
PeriodicOffsetLengthMismatch {
expected: usize,
found: usize,
},
#[error("Vertex key {key:?} not found in TDS (indicates TDS corruption or inconsistency)")]
VertexKeyNotFound {
key: VertexKey,
},
}
impl From<StackMatrixDispatchError> for SimplexValidationError {
fn from(source: StackMatrixDispatchError) -> Self {
CoordinateConversionError::from(source).into()
}
}
fn total_cmp_for_coordinate<T>(left: &T, right: &T) -> cmp::Ordering
where
T: CoordinateScalar,
{
left.ordered_partial_cmp(right)
.expect("CoordinateScalar::ordered_partial_cmp must define a total order")
}
fn compare_vertices_by_coordinates<T, U, const D: usize>(
left: &Vertex<T, U, D>,
right: &Vertex<T, U, D>,
) -> cmp::Ordering
where
T: CoordinateScalar,
{
for (left_coord, right_coord) in left.point().coords().iter().zip(right.point().coords()) {
let ordering = total_cmp_for_coordinate(left_coord, right_coord);
if ordering != cmp::Ordering::Equal {
return ordering;
}
}
cmp::Ordering::Equal
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum NeighborSlot {
Unassigned,
Boundary,
Neighbor(SimplexKey),
}
impl NeighborSlot {
#[inline]
#[must_use]
pub const fn from_neighbor_key(neighbor: Option<SimplexKey>) -> Self {
match neighbor {
Some(simplex_key) => Self::Neighbor(simplex_key),
None => Self::Boundary,
}
}
#[inline]
#[must_use]
pub const fn simplex_key(self) -> Option<SimplexKey> {
match self {
Self::Neighbor(simplex_key) => Some(simplex_key),
Self::Boundary | Self::Unassigned => None,
}
}
#[inline]
#[must_use]
pub const fn is_boundary(self) -> bool {
matches!(self, Self::Boundary)
}
#[inline]
#[must_use]
pub const fn is_unassigned(self) -> bool {
matches!(self, Self::Unassigned)
}
}
#[derive(Clone, Debug)]
pub struct Simplex<T, U, V, const D: usize> {
vertices: SimplexVertexKeyBuffer,
uuid: Uuid,
neighbors: Option<NeighborBuffer<NeighborSlot>>,
pub(crate) data: Option<V>,
pub(crate) periodic_vertex_offsets: Option<PeriodicOffsetBuffer<D>>,
_phantom: PhantomData<(T, U)>,
}
impl<T, U, V, const D: usize> Serialize for Simplex<T, U, V, D>
where
V: DataSerialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let has_data = self.data.is_some();
let field_count = if has_data { 2 } else { 1 };
let mut state = serializer.serialize_struct("Simplex", field_count)?;
state.serialize_field("uuid", &self.uuid)?;
if has_data {
state.serialize_field("data", &self.data)?;
}
state.end()
}
}
impl<'de, T, U, V, const D: usize> Deserialize<'de> for Simplex<T, U, V, D>
where
V: DataDeserialize,
{
fn deserialize<De>(deserializer: De) -> Result<Self, De::Error>
where
De: Deserializer<'de>,
{
struct SimplexVisitor<T, U, V, const D: usize>
where
V: DataDeserialize,
{
_phantom: PhantomData<(T, U, V)>,
}
impl<'de, T, U, V, const D: usize> Visitor<'de> for SimplexVisitor<T, U, V, D>
where
V: DataDeserialize,
{
type Value = Simplex<T, U, V, D>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a Simplex struct")
}
fn visit_map<A>(self, mut map: A) -> Result<Simplex<T, U, V, D>, A::Error>
where
A: MapAccess<'de>,
{
let mut uuid = None;
let mut data = None;
while let Some(key) = map.next_key()? {
match key {
"uuid" => {
if uuid.is_some() {
return Err(de::Error::duplicate_field("uuid"));
}
uuid = Some(map.next_value()?);
}
"data" => {
if data.is_some() {
return Err(de::Error::duplicate_field("data"));
}
data = Some(map.next_value()?);
}
_ => {
let _ = map.next_value::<IgnoredAny>()?;
}
}
}
let uuid: Uuid = uuid.ok_or_else(|| de::Error::missing_field("uuid"))?;
validate_uuid(&uuid)
.map_err(|e| de::Error::custom(format!("invalid uuid: {e}")))?;
let data = data.flatten();
let vertices = SimplexVertexKeyBuffer::new();
Ok(Simplex {
vertices,
uuid,
neighbors: None, data,
periodic_vertex_offsets: None,
_phantom: PhantomData,
})
}
}
const FIELDS: &[&str] = &["uuid", "data"];
deserializer.deserialize_struct(
"Simplex",
FIELDS,
SimplexVisitor {
_phantom: PhantomData,
},
)
}
}
impl<T, U, V, const D: usize> Simplex<T, U, V, D> {
pub(crate) fn new(
vertices: impl Into<SimplexVertexKeyBuffer>,
data: Option<V>,
) -> Result<Self, SimplexValidationError> {
let vertices = vertices.into();
let actual = vertices.len();
if actual != D + 1 {
return Err(SimplexValidationError::InsufficientVertices {
actual,
expected: D + 1,
dimension: D,
});
}
let mut seen: FastHashSet<VertexKey> = FastHashSet::default();
for &vkey in &vertices {
if !seen.insert(vkey) {
return Err(SimplexValidationError::DuplicateVertices);
}
}
Ok(Self {
vertices,
uuid: make_uuid(),
neighbors: None,
data,
periodic_vertex_offsets: None,
_phantom: PhantomData,
})
}
#[inline]
pub fn contains_vertex(&self, vkey: VertexKey) -> bool {
self.vertices.contains(&vkey)
}
#[inline]
pub fn has_vertex_in_common(&self, other: &Self) -> bool {
self.vertices
.iter()
.any(|vkey| other.vertices.contains(vkey))
}
#[inline]
pub fn vertices_enumerated(&self) -> impl Iterator<Item = (usize, &VertexKey)> {
self.vertices.iter().enumerate()
}
#[inline]
#[must_use]
pub fn neighbors(&self) -> Option<impl ExactSizeIterator<Item = Option<SimplexKey>> + '_> {
self.neighbor_keys()
}
#[inline]
#[must_use]
pub(crate) fn neighbor_keys(
&self,
) -> Option<impl ExactSizeIterator<Item = Option<SimplexKey>> + '_> {
self.neighbors
.as_ref()
.map(|slots| slots.iter().map(|slot| slot.simplex_key()))
}
#[inline]
#[must_use]
pub fn neighbor_key(&self, facet_idx: usize) -> Option<Option<SimplexKey>> {
self.neighbors
.as_ref()
.and_then(|slots| slots.get(facet_idx))
.map(|slot| slot.simplex_key())
}
#[inline]
#[must_use]
pub const fn neighbor_slots(&self) -> Option<&NeighborBuffer<NeighborSlot>> {
self.neighbors.as_ref()
}
#[inline]
pub(crate) const fn neighbor_slots_mut(&mut self) -> Option<&mut NeighborBuffer<NeighborSlot>> {
self.neighbors.as_mut()
}
#[inline]
pub fn vertices(&self) -> &[VertexKey] {
&self.vertices[..]
}
#[inline]
pub fn periodic_vertex_offsets(&self) -> Option<&[[i8; D]]> {
self.periodic_vertex_offsets.as_deref()
}
#[inline]
pub(crate) fn set_periodic_vertex_offsets(
&mut self,
offsets: impl Into<PeriodicOffsetBuffer<D>>,
) -> Result<(), SimplexValidationError> {
let offsets = offsets.into();
let found = offsets.len();
let expected = self.vertices.len();
if found != expected {
return Err(SimplexValidationError::PeriodicOffsetLengthMismatch { expected, found });
}
self.periodic_vertex_offsets = Some(offsets);
Ok(())
}
#[inline]
pub(crate) fn mirror_facet_index(
&self,
facet_idx: usize,
neighbor_simplex: &Self,
) -> Option<usize> {
if facet_idx >= self.vertices.len() {
return None;
}
debug_assert_eq!(
self.vertices().len(),
neighbor_simplex.vertices().len(),
"mirror_facet_index requires simplices with matching vertex counts",
);
let mut facet_vertices: SimplexVertexKeyBuffer = SimplexVertexKeyBuffer::new();
for (i, &vkey) in self.vertices().iter().enumerate() {
if i != facet_idx {
facet_vertices.push(vkey);
}
}
let mut mirror_idx: Option<usize> = None;
for (idx, &neighbor_vkey) in neighbor_simplex.vertices().iter().enumerate() {
if !facet_vertices.contains(&neighbor_vkey) {
if mirror_idx.is_some() {
return None;
}
mirror_idx = Some(idx);
}
}
mirror_idx
}
#[inline]
pub(crate) fn push_vertex_key(&mut self, vertex_key: VertexKey) {
self.vertices.push(vertex_key);
self.periodic_vertex_offsets = None;
}
#[inline]
pub(crate) fn clear_vertex_keys(&mut self) {
self.vertices.clear();
self.periodic_vertex_offsets = None;
}
#[inline]
pub(crate) fn swap_vertex_slots(&mut self, index_a: usize, index_b: usize) {
let max_idx = index_a.max(index_b);
assert!(
max_idx < self.vertices.len(),
"swap_vertex_slots vertices index out of bounds: max index {max_idx} >= vertices.len() {}",
self.vertices.len(),
);
if let Some(neighbors) = self.neighbors.as_ref() {
assert!(
max_idx < neighbors.len(),
"swap_vertex_slots neighbors index out of bounds: max index {max_idx} >= neighbors.len() {}",
neighbors.len(),
);
}
if let Some(offsets) = self.periodic_vertex_offsets.as_ref() {
assert!(
max_idx < offsets.len(),
"swap_vertex_slots periodic offsets index out of bounds: max index {max_idx} >= periodic_vertex_offsets.len() {}",
offsets.len(),
);
}
self.vertices.swap(index_a, index_b);
if let Some(neighbors) = &mut self.neighbors {
neighbors.swap(index_a, index_b);
}
if let Some(offsets) = &mut self.periodic_vertex_offsets {
offsets.swap(index_a, index_b);
}
}
#[inline]
pub(crate) fn set_neighbors_from_keys(
&mut self,
neighbors: impl IntoIterator<Item = Option<SimplexKey>>,
) -> Result<(), SimplexValidationError> {
let mut slots = NeighborBuffer::new();
slots.extend(neighbors.into_iter().map(NeighborSlot::from_neighbor_key));
if slots.len() != D + 1 {
return Err(SimplexValidationError::InvalidNeighborsLength {
actual: slots.len(),
expected: D + 1,
dimension: D,
});
}
self.neighbors = Some(slots);
Ok(())
}
#[inline]
pub(crate) fn ensure_neighbors_buffer_mut(&mut self) -> &mut NeighborBuffer<NeighborSlot> {
debug_assert!(
self.neighbors.as_ref().is_none_or(|buf| buf.len() == D + 1),
"neighbors buffer must always have length D+1"
);
self.neighbors.get_or_insert_with(|| {
let mut buffer = NeighborBuffer::new();
buffer.resize(D + 1, NeighborSlot::Unassigned);
buffer
})
}
}
impl<T, U, V, const D: usize> Simplex<T, U, V, D> {
#[inline]
pub fn number_of_vertices(&self) -> usize {
self.vertices.len()
}
#[inline]
pub const fn uuid(&self) -> Uuid {
self.uuid
}
#[inline]
#[must_use]
pub const fn data(&self) -> Option<&V> {
self.data.as_ref()
}
#[cfg(test)]
pub(crate) fn set_uuid(&mut self, uuid: Uuid) -> Result<(), SimplexValidationError> {
validate_uuid(&uuid)?;
self.uuid = uuid;
Ok(())
}
#[inline]
pub(crate) fn clear_neighbors(&mut self) {
self.neighbors = None;
}
#[inline]
pub fn vertex_uuids(
&self,
tds: &Tds<T, U, V, D>,
) -> Result<SimplexVertexUuidBuffer, SimplexValidationError> {
self.vertices
.iter()
.map(|&vkey| {
tds.vertex(vkey)
.map(Vertex::uuid)
.ok_or(SimplexValidationError::VertexKeyNotFound { key: vkey })
})
.collect()
}
#[inline]
pub fn vertex_uuid_iter<'a>(
&'a self,
tds: &'a Tds<T, U, V, D>,
) -> impl ExactSizeIterator<Item = Result<Uuid, SimplexValidationError>> + 'a {
self.vertices.iter().map(move |&vkey| {
tds.vertex(vkey)
.map(Vertex::uuid)
.ok_or(SimplexValidationError::VertexKeyNotFound { key: vkey })
})
}
#[inline]
pub const fn dim(&self) -> usize {
D
}
#[must_use]
pub fn into_hashmap<I>(simplices: I) -> FastHashMap<Uuid, Self>
where
I: IntoIterator<Item = Self>,
{
simplices.into_iter().map(|c| (c.uuid, c)).collect()
}
pub fn is_valid(&self) -> Result<(), SimplexValidationError> {
validate_uuid(&self.uuid)?;
if self.vertices.len() != D + 1 {
return Err(SimplexValidationError::InsufficientVertices {
actual: self.vertices.len(),
expected: D + 1,
dimension: D,
});
}
let mut seen: FastHashSet<VertexKey> = FastHashSet::default();
for &vkey in &self.vertices {
if !seen.insert(vkey) {
return Err(SimplexValidationError::DuplicateVertices);
}
}
if let Some(ref neighbors) = self.neighbors
&& neighbors.len() != D + 1
{
return Err(SimplexValidationError::InvalidNeighborsLength {
actual: neighbors.len(),
expected: D + 1,
dimension: D,
});
}
if let Some(ref neighbors) = self.neighbors {
for (facet_index, slot) in neighbors.iter().enumerate() {
if slot.is_unassigned() {
return Err(SimplexValidationError::UnassignedNeighborSlot { facet_index });
}
}
}
Ok(())
}
}
impl<T, U, V, const D: usize> Simplex<T, U, V, D> {
pub fn facet_views_from_tds(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
) -> Result<Vec<FacetView<'_, T, U, V, D>>, FacetError> {
let simplex = tds
.simplex(simplex_key)
.ok_or(FacetError::SimplexNotFoundInTriangulation)?;
let vertex_count = simplex.number_of_vertices();
if vertex_count > u8::MAX as usize {
return Err(FacetError::InvalidFacetIndex {
index: u8::MAX,
facet_count: vertex_count,
});
}
let mut facet_views = Vec::with_capacity(vertex_count);
for idx in 0..vertex_count {
let facet_index = usize_to_u8(idx, vertex_count)?;
facet_views.push(FacetView::new(tds, simplex_key, facet_index)?);
}
Ok(facet_views)
}
pub fn eq_by_vertices(
&self,
self_tds: &Tds<T, U, V, D>,
other: &Self,
other_tds: &Tds<T, U, V, D>,
) -> bool
where
T: CoordinateScalar,
{
let self_vertices: Option<Vec<_>> = self
.vertices()
.iter()
.map(|&vkey| self_tds.vertex(vkey))
.collect();
let other_vertices: Option<Vec<_>> = other
.vertices()
.iter()
.map(|&vkey| other_tds.vertex(vkey))
.collect();
let (Some(mut self_vertices), Some(mut other_vertices)) = (self_vertices, other_vertices)
else {
return false;
};
self_vertices.sort_by(|a, b| compare_vertices_by_coordinates(a, b));
other_vertices.sort_by(|a, b| compare_vertices_by_coordinates(a, b));
self_vertices == other_vertices
}
pub fn facet_view_iter(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
) -> Result<
impl ExactSizeIterator<Item = Result<FacetView<'_, T, U, V, D>, FacetError>>,
FacetError,
> {
let simplex = tds
.simplex(simplex_key)
.ok_or(FacetError::SimplexNotFoundInTriangulation)?;
let vertex_count = simplex.number_of_vertices();
if vertex_count > u8::MAX as usize {
return Err(FacetError::InvalidFacetIndex {
index: u8::MAX,
facet_count: vertex_count,
});
}
Ok((0..vertex_count).map(move |idx| {
let facet_index = usize_to_u8(idx, vertex_count)?;
FacetView::new(tds, simplex_key, facet_index)
}))
}
}
impl<T, U, V, const D: usize> PartialEq for Simplex<T, U, V, D> {
#[inline]
fn eq(&self, other: &Self) -> bool {
let mut self_keys: SimplexVertexKeyBuffer = self.vertices.iter().copied().collect();
let mut other_keys: SimplexVertexKeyBuffer = other.vertices.iter().copied().collect();
self_keys.sort_unstable();
other_keys.sort_unstable();
self_keys == other_keys
}
}
impl<T, U, V, const D: usize> PartialOrd for Simplex<T, U, V, D> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
let mut self_keys: SimplexVertexKeyBuffer = self.vertices.iter().copied().collect();
let mut other_keys: SimplexVertexKeyBuffer = other.vertices.iter().copied().collect();
self_keys.sort_unstable();
other_keys.sort_unstable();
self_keys.partial_cmp(&other_keys)
}
}
impl<T, U, V, const D: usize> Eq for Simplex<T, U, V, D> {}
impl<T, U, V, const D: usize> Hash for Simplex<T, U, V, D> {
fn hash<H: Hasher>(&self, state: &mut H) {
let mut sorted_keys: SimplexVertexKeyBuffer = self.vertices.iter().copied().collect();
sorted_keys.sort_unstable();
for key in sorted_keys {
key.hash(state);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::builder::DelaunayTriangulationBuilder;
use crate::construction::{
ConstructionOptions, InitialSimplexStrategy, InsertionOrderStrategy,
};
use crate::core::facet::FacetError;
use crate::core::validation::TopologyGuarantee;
use crate::core::vertex::vertex;
use crate::geometry::kernel::AdaptiveKernel;
use crate::geometry::matrix::MAX_STACK_MATRIX_DIM;
use crate::geometry::point::Point;
use crate::geometry::predicates::insphere;
use crate::geometry::util::{circumcenter, circumradius, circumradius_with_center};
use crate::prelude::DelaunayTriangulation;
use approx::assert_relative_eq;
use std::{
cmp,
collections::{HashSet, hash_map::DefaultHasher},
hash::Hasher,
};
type TestVertex3D = Vertex<f64, (), 3>;
type TestVertex2D = Vertex<f64, (), 2>;
struct NonDataType(String);
fn simplex_with_non_data_type_metadata<const D: usize>(
vertex_ids: [u64; D],
data: NonDataType,
) -> Simplex<f64, String, NonDataType, D> {
Simplex {
vertices: vertex_ids
.into_iter()
.map(|id| VertexKey::from(slotmap::KeyData::from_ffi(id)))
.collect(),
uuid: make_uuid(),
neighbors: None,
data: Some(data),
periodic_vertex_offsets: None,
_phantom: PhantomData,
}
}
macro_rules! test_simplex_dimensions {
($(
$test_name:ident => $dim:expr => $vertices:expr
),+ $(,)?) => {
$(
#[test]
fn $test_name() {
let vertices = $vertices;
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex) = dt.simplices().next().unwrap();
assert_simplex_properties(simplex, $dim + 1, $dim);
}
pastey::paste! {
#[test]
fn [<$test_name _with_data>]() {
let vertices = $vertices;
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, (), i32, $dim> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex = simplex_ref.clone();
simplex.data = Some(42);
assert_simplex_properties(&simplex, $dim + 1, $dim);
assert_eq!(simplex.data, Some(42));
}
#[test]
fn [<$test_name _serialization_roundtrip>]() {
let vertices = $vertices;
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, (), i32, $dim> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let (_, simplex) = dt.simplices().next().unwrap();
let mut simplex = simplex.clone();
simplex.data = Some(99);
let serialized = serde_json::to_string(&simplex).unwrap();
assert!(serialized.contains("\"data\":"));
let deserialized: Simplex<f64, (), i32, $dim> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.data, Some(99));
assert_eq!(deserialized.uuid(), simplex.uuid());
let vertices = $vertices;
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex) = dt.simplices().next().unwrap();
let serialized = serde_json::to_string(&simplex).unwrap();
assert!(!serialized.contains("\"data\":"));
let deserialized: Simplex<f64, (), Option<i32>, $dim> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.data, None);
}
#[test]
fn [<$test_name _uuid_uniqueness>]() {
let vertices1 = $vertices;
let vertices2 = $vertices;
let dt1 = DelaunayTriangulation::new(&vertices1).unwrap();
let (_, simplex1) = dt1.simplices().next().unwrap();
let dt2 = DelaunayTriangulation::new(&vertices2).unwrap();
let (_, simplex2) = dt2.simplices().next().unwrap();
assert_ne!(simplex1.uuid(), simplex2.uuid());
assert!(!simplex1.uuid().is_nil());
assert!(!simplex2.uuid().is_nil());
}
}
)+
};
}
#[test]
fn equality_ordering_and_hash_do_not_require_data_type_metadata() {
let simplex_a =
simplex_with_non_data_type_metadata([1, 2, 3], NonDataType("left".to_string()));
let simplex_b =
simplex_with_non_data_type_metadata([3, 2, 1], NonDataType("right".to_string()));
let simplex_c =
simplex_with_non_data_type_metadata([1, 2, 4], NonDataType("other".to_string()));
assert!(simplex_a == simplex_b);
assert!(simplex_a != simplex_c);
assert_eq!(
simplex_a.partial_cmp(&simplex_b),
Some(cmp::Ordering::Equal)
);
let mut hash_a = DefaultHasher::new();
let mut hash_b = DefaultHasher::new();
simplex_a.hash(&mut hash_a);
simplex_b.hash(&mut hash_b);
assert_eq!(hash_a.finish(), hash_b.finish());
assert_eq!(simplex_a.data.as_ref().unwrap().0, "left");
assert_eq!(simplex_b.data.as_ref().unwrap().0, "right");
}
test_simplex_dimensions! {
simplex_2d => 2 => vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
],
simplex_3d => 3 => 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]),
],
simplex_4d => 4 => vec![
vertex!([0.0, 0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 0.0, 1.0]),
],
simplex_5d => 5 => vec![
vertex!([0.0, 0.0, 0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 0.0, 0.0, 1.0]),
],
}
fn assert_simplex_properties<T, U, V, const D: usize>(
simplex: &Simplex<T, U, V, D>,
expected_vertices: usize,
expected_dim: usize,
) {
assert_eq!(simplex.number_of_vertices(), expected_vertices);
assert_eq!(simplex.dim(), expected_dim);
assert!(!simplex.uuid().is_nil());
}
fn create_test_vertices_3d() -> Vec<TestVertex3D> {
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]),
]
}
fn create_test_vertices_2d() -> Vec<TestVertex2D> {
vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
]
}
#[test]
fn simplex_macro_without_data() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]), ];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex) = dt.simplices().next().unwrap();
assert_eq!(simplex.number_of_vertices(), 4);
assert_eq!(simplex.dim(), 3);
assert!(simplex.data.is_none());
assert!(!simplex.uuid().is_nil());
let simplex_coords: Vec<Vec<f64>> = simplex
.vertices()
.iter()
.map(|&vkey| {
dt.tds()
.vertex(vkey)
.unwrap()
.point()
.coords()
.as_slice()
.to_vec()
})
.collect();
for original in &vertices {
let original_coords = original.point().coords().as_slice();
assert!(
simplex_coords.iter().any(|coords| {
coords
.iter()
.zip(original_coords)
.all(|(a, b)| (a - b).abs() < f64::EPSILON)
}),
"Input vertex {original_coords:?} not found in simplex"
);
}
println!("Simplex without data: {simplex:?}");
}
#[test]
fn test_eq_by_vertices_same_coordinates_different_tds() {
println!("Testing eq_by_vertices with same coordinates across different TDS");
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 dt1 = DelaunayTriangulation::new(&vertices).unwrap();
let dt2 = DelaunayTriangulation::new(&vertices).unwrap();
let simplex1 = dt1.simplices().next().unwrap().1;
let simplex2 = dt2.simplices().next().unwrap().1;
assert!(simplex1.eq_by_vertices(dt1.tds(), simplex2, dt2.tds()));
println!(" ✓ Simplices from different DT with same coordinates are equal");
}
#[test]
fn test_eq_by_vertices_2d() {
println!("Testing eq_by_vertices in 2D");
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt1 = DelaunayTriangulation::new(&vertices).unwrap();
let dt2 = DelaunayTriangulation::new(&vertices).unwrap();
let simplex1 = dt1.simplices().next().unwrap().1;
let simplex2 = dt2.simplices().next().unwrap().1;
assert!(simplex1.eq_by_vertices(dt1.tds(), simplex2, dt2.tds()));
println!(" ✓ 2D simplices with same coordinates are equal");
}
#[test]
fn simplex_macro_with_data() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, (), i32, 3> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex = simplex_ref.clone();
simplex.data = Some(42);
assert_eq!(simplex.number_of_vertices(), 4);
assert_eq!(simplex.dim(), 3);
assert_eq!(simplex.data.unwrap(), 42);
assert!(!simplex.uuid().is_nil());
let simplex_coords: Vec<Vec<f64>> = simplex
.vertices()
.iter()
.map(|&vkey| {
dt.tds()
.vertex(vkey)
.unwrap()
.point()
.coords()
.as_slice()
.to_vec()
})
.collect();
for original in &vertices {
let original_coords = original.point().coords().as_slice();
assert!(
simplex_coords.iter().any(|coords| {
coords
.iter()
.zip(original_coords)
.all(|(a, b)| (a - b).abs() < f64::EPSILON)
}),
"Input vertex {original_coords:?} not found in simplex"
);
}
println!("Simplex with data: {simplex:?}");
}
#[test]
fn simplex_with_vertex_data() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0], 1),
vertex!([1.0, 0.0, 0.0], 2),
vertex!([0.0, 1.0, 0.0], 3),
vertex!([0.0, 0.0, 1.0], 4), ];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, (), 3> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let (_, simplex) = dt.simplices().next().unwrap();
assert_eq!(simplex.number_of_vertices(), 4);
assert_eq!(simplex.dim(), 3);
let simplex_data: Vec<i32> = simplex
.vertices()
.iter()
.map(|&vkey| dt.tds().vertex(vkey).unwrap().data.unwrap())
.collect();
for expected in &[1, 2, 3, 4] {
assert!(
simplex_data.contains(expected),
"Expected vertex data {expected} not found in simplex"
);
}
}
#[test]
fn simplex_partial_eq() {
let vertices = vec![
vertex!([0.0, 0.0, 1.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 0.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex1) = dt.simplices().next().unwrap();
let simplex2 = simplex1.clone();
assert_eq!(*simplex1, simplex2);
assert_eq!(simplex1.uuid(), simplex2.uuid()); assert_eq!(simplex1.vertices(), simplex2.vertices());
let simplex3 = simplex1.clone();
assert_eq!(*simplex1, simplex3);
}
#[test]
fn simplex_partial_ord() {
let all_vertices = vec![
vertex!([0.0, 0.0, 1.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 1.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&all_vertices).unwrap();
let simplices: Vec<_> = dt.simplices().map(|(_, simplex)| simplex).collect();
if simplices.len() >= 2 {
let simplex1 = simplices[0];
let simplex2 = simplices[1];
let has_ordering = simplex1 != simplex2 || simplex1 == simplex2;
assert!(
has_ordering,
"Simplices should have some ordering relationship"
);
}
}
#[test]
fn simplex_hash() {
let vertices = vec![
vertex!([0.0, 0.0, 1.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 0.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex1) = dt.simplices().next().unwrap();
let simplex2 = simplex1.clone();
let mut hasher1 = DefaultHasher::new();
let mut hasher2 = DefaultHasher::new();
simplex1.hash(&mut hasher1);
simplex2.hash(&mut hasher2);
assert_eq!(*simplex1, simplex2); assert_eq!(hasher1.finish(), hasher2.finish()); assert_eq!(simplex1.uuid(), simplex2.uuid());
}
#[test]
fn simplex_clone() {
let vertices = vec![
vertex!([0.0, 0.0, 1.0], 1),
vertex!([0.0, 1.0, 0.0], 1),
vertex!([1.0, 0.0, 0.0], 1),
vertex!([1.0, 1.0, 1.0], 2),
];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, i32, 3> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex1 = simplex_ref.clone();
simplex1.data = Some(42);
let simplex2 = simplex1.clone();
assert_eq!(simplex1, simplex2);
}
#[test]
fn simplex_ordering_edge_cases() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex1) = dt.simplices().next().unwrap();
let simplex2 = simplex1.clone();
assert_ne!(simplex1.partial_cmp(&simplex2), Some(cmp::Ordering::Less));
assert_ne!(simplex2.partial_cmp(simplex1), Some(cmp::Ordering::Less));
assert!(*simplex1 <= simplex2);
assert!(simplex2 <= *simplex1);
assert!(*simplex1 >= simplex2);
assert!(simplex2 >= *simplex1);
}
#[test]
fn simplex_number_of_vertices() {
let vertices_2d = create_test_vertices_2d();
let dt_2d = DelaunayTriangulation::new(&vertices_2d).unwrap();
let simplex_key_2d = dt_2d.tds().simplex_keys().next().unwrap();
let triangle = dt_2d.tds().simplex(simplex_key_2d).unwrap();
assert_eq!(triangle.number_of_vertices(), 3);
let vertices_3d = create_test_vertices_3d();
let dt_3d = DelaunayTriangulation::new(&vertices_3d).unwrap();
let simplex_key_3d = dt_3d.tds().simplex_keys().next().unwrap();
let tetrahedron = dt_3d.tds().simplex(simplex_key_3d).unwrap();
assert_eq!(tetrahedron.number_of_vertices(), 4);
}
#[test]
fn simplex_mirror_facet_index_shared_facet_2d() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 1.1]), ];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let simplices: Vec<_> = dt.simplices().map(|(_, simplex)| simplex).collect();
assert!(
simplices.len() >= 2,
"Expected at least 2 simplices, got {}",
simplices.len()
);
let mut found = None;
for i in 0..simplices.len() {
for j in (i + 1)..simplices.len() {
let simplex_a = simplices[i];
let simplex_b = simplices[j];
let shared: FastHashSet<VertexKey> = simplex_a
.vertices()
.iter()
.copied()
.filter(|v| simplex_b.vertices().contains(v))
.collect();
if shared.len() == 2 {
let facet_idx_a = simplex_a
.vertices()
.iter()
.position(|v| !shared.contains(v))
.expect("simplex_a should have one vertex not in the shared facet");
let facet_idx_b = simplex_b
.vertices()
.iter()
.position(|v| !shared.contains(v))
.expect("simplex_b should have one vertex not in the shared facet");
found = Some((simplex_a, simplex_b, facet_idx_a, facet_idx_b));
break;
}
}
if found.is_some() {
break;
}
}
let Some((simplex_a, simplex_b, facet_idx_a, facet_idx_b)) = found else {
panic!("Expected to find a pair of neighboring simplices that share an edge");
};
assert_eq!(
simplex_a.mirror_facet_index(facet_idx_a, simplex_b),
Some(facet_idx_b)
);
assert_eq!(
simplex_b.mirror_facet_index(facet_idx_b, simplex_a),
Some(facet_idx_a)
);
assert_eq!(
simplex_a.mirror_facet_index(simplex_a.number_of_vertices(), simplex_b),
None
);
}
#[test]
fn simplex_mirror_facet_index_returns_none_when_simplices_do_not_share_facet_2d() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 1.0]),
vertex!([0.5, 0.5]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let simplices: Vec<_> = dt.simplices().map(|(_, simplex)| simplex).collect();
assert!(
simplices.len() >= 3,
"Expected at least 3 simplices, got {}",
simplices.len()
);
let mut non_adjacent = None;
'outer: for i in 0..simplices.len() {
for j in (i + 1)..simplices.len() {
let simplex_a = simplices[i];
let simplex_b = simplices[j];
let shared_count = simplex_a
.vertices()
.iter()
.filter(|v| simplex_b.vertices().contains(v))
.count();
if shared_count < 2 {
non_adjacent = Some((simplex_a, simplex_b));
break 'outer;
}
}
}
let Some((simplex_a, simplex_b)) = non_adjacent else {
panic!("Expected to find a pair of non-adjacent simplices");
};
assert_eq!(simplex_a.mirror_facet_index(0, simplex_b), None);
}
#[test]
fn simplex_dim() {
let vertices_2d = create_test_vertices_2d();
let dt_2d = DelaunayTriangulation::new(&vertices_2d).unwrap();
let simplex_key_2d = dt_2d.tds().simplex_keys().next().unwrap();
let triangle = dt_2d.tds().simplex(simplex_key_2d).unwrap();
assert_eq!(triangle.dim(), 2);
let vertices_3d = create_test_vertices_3d();
let dt_3d = DelaunayTriangulation::new(&vertices_3d).unwrap();
let simplex_key_3d = dt_3d.tds().simplex_keys().next().unwrap();
let tetrahedron = dt_3d.tds().simplex(simplex_key_3d).unwrap();
assert_eq!(tetrahedron.dim(), 3);
}
#[test]
fn simplex_contains_vertex() {
let vertex1: Vertex<f64, i32, 3> = vertex!([0.0, 0.0, 1.0], 1);
let vertex2 = vertex!([0.0, 1.0, 0.0], 1);
let vertex3 = vertex!([1.0, 0.0, 0.0], 1);
let vertex4 = vertex!([1.0, 1.0, 1.0], 2);
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, (), 3> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_keys: Vec<_> = dt.tds().vertices().map(|(k, _)| k).collect();
assert!(simplex.contains_vertex(vertex_keys[0]));
assert!(simplex.contains_vertex(vertex_keys[1]));
assert!(simplex.contains_vertex(vertex_keys[2]));
assert!(simplex.contains_vertex(vertex_keys[3]));
println!("Simplex: {simplex:?}");
}
#[test]
fn simplex_has_vertex_in_common() {
let vertices1 = vec![
vertex!([0.0, 0.0, 1.0], 1),
vertex!([0.0, 1.0, 0.0], 1),
vertex!([1.0, 0.0, 0.0], 1),
vertex!([1.0, 1.0, 1.0], 2),
];
let tds1: DelaunayTriangulation<AdaptiveKernel<f64>, i32, i32, 3> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices1).unwrap();
let (_, simplex_ref) = tds1.simplices().next().unwrap();
let mut simplex = simplex_ref.clone();
simplex.data = Some(42);
let vertices2 = vec![
vertex!([0.0, 0.0, 1.0], 1),
vertex!([0.0, 1.0, 0.0], 1),
vertex!([1.0, 0.0, 0.0], 1),
vertex!([0.0, 0.0, 0.0], 0),
];
let tds2: DelaunayTriangulation<AdaptiveKernel<f64>, i32, i32, 3> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices2).unwrap();
let (_, simplex2_ref) = tds2.simplices().next().unwrap();
let mut simplex2 = simplex2_ref.clone();
simplex2.data = Some(43);
assert!(simplex.has_vertex_in_common(&simplex2));
println!("Simplex: {simplex:?}");
}
#[test]
fn test_vertex_uuids_success() {
let vertex1 = vertex!([0.0, 0.0, 0.0], 10);
let vertex2 = vertex!([1.0, 0.0, 0.0], 20);
let vertex3 = vertex!([0.0, 1.0, 0.0], 30);
let vertex4 = vertex!([0.0, 0.0, 1.0], 40);
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, (), 3> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_uuids = simplex.vertex_uuids(dt.tds()).unwrap();
assert_eq!(simplex.vertex_uuid_iter(dt.tds()).count(), 4);
for (expected_uuid, returned_uuid) in
simplex.vertex_uuid_iter(dt.tds()).zip(vertex_uuids.iter())
{
assert_eq!(expected_uuid.unwrap(), *returned_uuid);
}
let unique_uuids: HashSet<_> = vertex_uuids.iter().collect();
assert_eq!(unique_uuids.len(), vertex_uuids.len());
for uuid in simplex.vertex_uuid_iter(dt.tds()) {
assert_ne!(uuid.unwrap(), Uuid::nil());
}
println!("✓ vertex_uuids method returns correct vertex UUIDs");
}
#[test]
fn test_vertex_uuids_empty_simplex_fails() {
let vertices = vec![vertex!([0.0, 0.0, 0.0])];
let result = DelaunayTriangulation::new(&vertices);
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("Insufficient vertices"));
println!("✓ DT construction properly validates vertex count");
}
#[test]
fn test_vertex_uuids_2d_simplex() {
let vertex1 = vertex!([0.0, 0.0], 1);
let vertex2 = vertex!([1.0, 0.0], 2);
let vertex3 = vertex!([0.5, 1.0], 3);
let vertices = vec![vertex1, vertex2, vertex3];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, (), 2> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_uuids = simplex.vertex_uuids(dt.tds()).unwrap();
assert_eq!(simplex.vertex_uuid_iter(dt.tds()).count(), 3);
for (expected_uuid, returned_uuid) in
simplex.vertex_uuid_iter(dt.tds()).zip(vertex_uuids.iter())
{
assert_eq!(expected_uuid.unwrap(), *returned_uuid);
}
let unique_uuids: HashSet<_> = vertex_uuids.iter().collect();
assert_eq!(unique_uuids.len(), vertex_uuids.len());
for uuid in simplex.vertex_uuid_iter(dt.tds()) {
assert_ne!(uuid.unwrap(), Uuid::nil());
}
println!("✓ vertex_uuids works correctly for 2D simplices");
}
#[test]
fn test_vertex_uuids_4d_simplex() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0, 0.0], 1),
vertex!([1.0, 0.0, 0.0, 0.0], 2),
vertex!([0.0, 1.0, 0.0, 0.0], 3),
vertex!([0.0, 0.0, 1.0, 0.0], 4),
vertex!([0.0, 0.0, 0.0, 1.0], 5),
];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, (), 4> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_uuids = simplex.vertex_uuids(dt.tds()).unwrap();
assert_eq!(simplex.vertex_uuid_iter(dt.tds()).count(), 5);
for (expected_uuid, returned_uuid) in
simplex.vertex_uuid_iter(dt.tds()).zip(vertex_uuids.iter())
{
assert_eq!(expected_uuid.unwrap(), *returned_uuid);
}
let unique_uuids: HashSet<_> = vertex_uuids.iter().collect();
assert_eq!(unique_uuids.len(), vertex_uuids.len());
let vertex_data: Vec<i32> = simplex
.vertices()
.iter()
.map(|&vkey| dt.tds().vertex(vkey).unwrap().data.unwrap())
.collect();
for expected in 1..=5 {
assert!(
vertex_data.contains(&expected),
"Expected vertex data {expected} not found"
);
}
for uuid in simplex.vertex_uuid_iter(dt.tds()) {
assert_ne!(uuid.unwrap(), Uuid::nil());
}
println!("✓ vertex_uuids works correctly for 4D simplices");
}
#[test]
fn test_vertex_uuids_with_f32_coordinates() {
let vertices = vec![
vertex!([0.0f32, 0.0f32, 0.0f32]),
vertex!([1.0f32, 0.0f32, 0.0f32]),
vertex!([0.0f32, 1.0f32, 0.0f32]),
vertex!([0.0f32, 0.0f32, 1.0f32]),
];
let options = ConstructionOptions::default()
.with_insertion_order(InsertionOrderStrategy::Input)
.with_initial_simplex_strategy(InitialSimplexStrategy::First);
let dt: DelaunayTriangulation<AdaptiveKernel<f32>, (), (), 3> =
DelaunayTriangulation::with_topology_guarantee_and_options(
&AdaptiveKernel::new(),
&vertices,
TopologyGuarantee::DEFAULT,
options,
)
.unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_uuids = simplex.vertex_uuids(dt.tds()).unwrap();
assert_eq!(simplex.vertex_uuid_iter(dt.tds()).count(), 4);
for expected_coords in [
[0.0f32, 0.0f32, 0.0f32],
[1.0f32, 0.0f32, 0.0f32],
[0.0f32, 1.0f32, 0.0f32],
[0.0f32, 0.0f32, 1.0f32],
] {
assert!(
simplex.vertices().iter().any(|&vertex_key| {
dt.tds()
.vertex(vertex_key)
.unwrap()
.point()
.coords()
.iter()
.zip(expected_coords)
.all(|(actual, expected)| (*actual - expected).abs() <= f32::EPSILON)
}),
"Expected simplex coordinates {expected_coords:?} not found"
);
}
for (expected_uuid, returned_uuid) in
simplex.vertex_uuid_iter(dt.tds()).zip(vertex_uuids.iter())
{
assert_eq!(expected_uuid.unwrap(), *returned_uuid);
}
let unique_uuids: HashSet<_> = vertex_uuids.iter().collect();
assert_eq!(unique_uuids.len(), vertex_uuids.len());
for uuid in simplex.vertex_uuid_iter(dt.tds()) {
assert_ne!(uuid.unwrap(), Uuid::nil());
}
println!("✓ vertex_uuids works correctly with f32 coordinates");
}
#[test]
fn simplex_1d() {
let vertices = vec![vertex!([0.0]), vertex!([1.0])];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex) = dt.simplices().next().unwrap();
assert_simplex_properties(simplex, 2, 1);
}
#[test]
fn simplex_with_f32() {
let vertices = vec![
vertex!([0.0f32, 0.0f32]),
vertex!([1.0f32, 0.0f32]),
vertex!([0.0f32, 1.0f32]),
];
let dt: DelaunayTriangulation<AdaptiveKernel<f32>, (), (), 2> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let (_, simplex) = dt.simplices().next().unwrap();
assert_eq!(simplex.number_of_vertices(), 3);
assert_eq!(simplex.dim(), 2);
assert!(!simplex.uuid().is_nil());
}
#[test]
fn simplex_single_vertex() {
let vertices = vec![vertex!([0.0, 0.0, 0.0])];
let result = DelaunayTriangulation::new(&vertices);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Insufficient vertices"));
assert!(error_msg.contains('1'));
assert!(error_msg.contains('4'));
}
#[test]
fn simplex_neighbors_none_by_default() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex) = dt.simplices().next().unwrap();
assert!(simplex.neighbors.is_some() || simplex.neighbors.is_none());
}
#[test]
fn simplex_data_none_by_default() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex) = dt.simplices().next().unwrap();
assert!(simplex.data.is_none());
}
#[test]
fn simplex_data_can_be_set() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, (), i32, 3> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex = simplex_ref.clone();
simplex.data = Some(42);
assert_eq!(simplex.data.unwrap(), 42);
}
#[test]
fn simplex_into_hashmap_empty() {
let hashmap = Simplex::<f64, (), (), 3>::into_hashmap([]);
assert!(hashmap.is_empty());
}
#[test]
fn simplex_into_hashmap_multiple() {
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!([1.0, 1.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let mut simplices_iter = dt.simplices().map(|(_, simplex)| simplex.clone());
let simplex1 = simplices_iter
.next()
.expect("Need at least 2 simplices for this test");
let simplex2 = simplices_iter
.next()
.expect("Need at least 2 simplices for this test");
let uuid1 = simplex1.uuid();
let uuid2 = simplex2.uuid();
let hashmap = Simplex::into_hashmap(
std::iter::once(simplex1)
.chain(std::iter::once(simplex2))
.chain(simplices_iter),
);
assert!(hashmap.len() >= 2);
assert!(hashmap.contains_key(&uuid1));
assert!(hashmap.contains_key(&uuid2));
}
#[test]
fn simplex_debug_format() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, (), i32, 3> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex = simplex_ref.clone();
simplex.data = Some(42);
let debug_str = format!("{simplex:?}");
assert!(debug_str.contains("Simplex"));
assert!(!simplex.vertices().is_empty());
assert!(!simplex.uuid().is_nil());
assert_eq!(simplex.data.unwrap(), 42);
}
#[test]
fn simplex_to_and_from_json() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let serialized = serde_json::to_string(&dt).unwrap();
assert!(serialized.contains("vertices"));
assert!(serialized.contains("simplices"));
let tds: Tds<f64, (), (), 3> = serde_json::from_str(&serialized).unwrap();
let deserialized = DelaunayTriangulation::try_from_tds(tds, AdaptiveKernel::new())
.expect("serialized Delaunay TDS should validate");
assert_eq!(deserialized.number_of_vertices(), dt.number_of_vertices());
assert_eq!(deserialized.number_of_simplices(), dt.number_of_simplices());
assert_eq!(deserialized.dim(), dt.dim());
assert_ne!(deserialized.number_of_simplices(), 0);
for (_simplex_key, simplex) in deserialized.tds().simplices() {
assert_eq!(simplex.dim(), 3);
assert_eq!(simplex.number_of_vertices(), 4);
}
println!("TDS serialization/deserialization test passed");
}
#[test]
fn simplex_deserialization_error_cases() {
let invalid_json_missing_uuid = r#"{"data": null}"#;
let result: Result<Simplex<f64, (), (), 3>, _> =
serde_json::from_str(invalid_json_missing_uuid);
assert!(result.is_err(), "Missing UUID should cause error");
let error = result.unwrap_err().to_string();
assert!(
error.contains("missing field") || error.contains("uuid"),
"Error should mention missing uuid field: {error}"
);
let invalid_json_bad_uuid = r#"{"uuid": "not-a-valid-uuid"}"#;
let result: Result<Simplex<f64, (), (), 3>, _> =
serde_json::from_str(invalid_json_bad_uuid);
assert!(result.is_err(), "Invalid UUID format should cause error");
let invalid_json_syntax = r"{this is not valid JSON}";
let result: Result<Simplex<f64, (), (), 3>, _> = serde_json::from_str(invalid_json_syntax);
assert!(result.is_err(), "Invalid JSON syntax should cause error");
let empty_json = r"{}";
let result: Result<Simplex<f64, (), (), 3>, _> = serde_json::from_str(empty_json);
assert!(result.is_err(), "Empty JSON should fail (missing uuid)");
let json_unknown_field = r#"{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"unknown_field": "value",
"another_unknown": 123
}"#;
let result: Result<Simplex<f64, (), (), 3>, _> = serde_json::from_str(json_unknown_field);
assert!(result.is_ok(), "Unknown fields should be ignored");
}
#[test]
fn simplex_serialization_data_field_handling() {
let minimal_valid_json = r#"{"uuid": "550e8400-e29b-41d4-a716-446655440000"}"#;
let result: Result<Simplex<f64, (), (), 3>, _> = serde_json::from_str(minimal_valid_json);
assert!(
result.is_ok(),
"Minimal valid JSON with just UUID should succeed"
);
let simplex = result.unwrap();
assert_eq!(
simplex.uuid().to_string(),
"550e8400-e29b-41d4-a716-446655440000"
);
assert!(simplex.data.is_none());
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, (), i32, 3> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex_with_data = simplex_ref.clone();
simplex_with_data.data = Some(42);
let serialized = serde_json::to_string(&simplex_with_data).unwrap();
assert!(
serialized.contains("\"data\":"),
"Some(data) should include data field"
);
assert!(serialized.contains("42"));
let deserialized: Simplex<f64, (), i32, 3> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.data, Some(42));
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex_none) = dt.simplices().next().unwrap();
let serialized_none = serde_json::to_string(&simplex_none).unwrap();
assert!(
!serialized_none.contains("\"data\":"),
"None should omit data field"
);
let deserialized_none: Simplex<f64, (), Option<i32>, 3> =
serde_json::from_str(&serialized_none).unwrap();
assert_eq!(deserialized_none.data, None);
let json_with_null = r#"{"uuid":"550e8400-e29b-41d4-a716-446655440000","data":null}"#;
let simplex_explicit_null: Simplex<f64, (), Option<i32>, 3> =
serde_json::from_str(json_with_null).unwrap();
assert_eq!(simplex_explicit_null.data, None);
}
#[test]
fn simplex_coordinate_ranges() {
let negative_vertices = vec![
vertex!([-1.0, -1.0, -1.0]),
vertex!([-2.0, -1.0, -1.0]),
vertex!([-1.0, -2.0, -1.0]),
vertex!([-1.0, -1.0, -2.0]),
];
let dt_neg = DelaunayTriangulation::new(&negative_vertices).unwrap();
let (_, simplex_neg) = dt_neg.tds().simplices().next().unwrap();
assert_eq!(simplex_neg.number_of_vertices(), 4);
assert_eq!(simplex_neg.dim(), 3);
let large_vertices = vec![
vertex!([1e6, 1e6, 1e6]),
vertex!([2e6, 1e6, 1e6]),
vertex!([1e6, 2e6, 1e6]),
vertex!([1e6, 1e6, 2e6]),
];
let dt_large = DelaunayTriangulation::new(&large_vertices).unwrap();
let (_, simplex_large) = dt_large.tds().simplices().next().unwrap();
assert_eq!(simplex_large.number_of_vertices(), 4);
assert_eq!(simplex_large.dim(), 3);
let small_vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1e-3, 0.0, 0.0]),
vertex!([0.0, 1e-3, 0.0]),
vertex!([0.0, 0.0, 1e-3]),
];
let dt_small = DelaunayTriangulation::new(&small_vertices).unwrap();
let (_, simplex_small) = dt_small.tds().simplices().next().unwrap();
assert_eq!(simplex_small.number_of_vertices(), 4);
assert_eq!(simplex_small.dim(), 3);
}
#[test]
fn simplex_circumradius_2d() {
let vertex1 = vertex!([0.0, 0.0]);
let vertex2 = vertex!([1.0, 0.0]);
let vertex3 = vertex!([0.0, 1.0]);
let vertices = vec![vertex1, vertex2, vertex3];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_points: Vec<Point<f64, 2>> = simplex
.vertices()
.iter()
.map(|vk| *dt.tds().vertex(*vk).unwrap().point())
.collect();
let circumradius = circumradius(&vertex_points).unwrap();
let expected_radius = 2.0_f64.sqrt() / 2.0;
assert_relative_eq!(circumradius, expected_radius, epsilon = 1e-10);
}
#[test]
fn simplex_contains_vertex_false() {
let vertex1 = vertex!([0.0, 0.0, 0.0]);
let vertex2 = vertex!([1.0, 0.0, 0.0]);
let vertex3 = vertex!([0.0, 1.0, 0.0]);
let vertex4 = vertex!([0.0, 0.0, 1.0]); let vertex_outside: Vertex<f64, (), 3> = vertex!([2.0, 2.0, 2.0]);
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let outside_key = dt.tds().vertex_key_from_uuid(&vertex_outside.uuid());
assert!(outside_key.is_none() || !simplex.contains_vertex(outside_key.unwrap()));
}
#[test]
fn simplex_circumsphere_contains_vertex_determinant() {
let vertex1 = vertex!([0.0, 0.0, 0.0], 1);
let vertex2 = vertex!([1.0, 0.0, 0.0], 1);
let vertex3 = vertex!([0.0, 1.0, 0.0], 1);
let vertex4 = vertex!([0.0, 0.0, 1.0], 2);
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, (), 3> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_far_outside: Vertex<f64, i32, 3> = vertex!([10.0, 10.0, 10.0], 4);
let vertex_points: Vec<Point<f64, 3>> = simplex
.vertices()
.iter()
.map(|vk| *dt.tds().vertex(*vk).unwrap().point())
.collect();
let result = insphere(&vertex_points, *vertex_far_outside.point());
assert!(result.is_ok());
let origin: Vertex<f64, i32, 3> = vertex!([0.0, 0.0, 0.0], 3);
let vertex_points: Vec<Point<f64, 3>> = simplex
.vertices()
.iter()
.map(|vk| *dt.tds().vertex(*vk).unwrap().point())
.collect();
let result_origin = insphere(&vertex_points, *origin.point());
assert!(result_origin.is_ok());
}
#[test]
fn simplex_circumsphere_contains_vertex_2d() {
let vertex1 = vertex!([0.0, 0.0]);
let vertex2 = vertex!([1.0, 0.0]);
let vertex3 = vertex!([0.0, 1.0]);
let vertices = vec![vertex1, vertex2, vertex3];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_far_outside: Vertex<f64, (), 2> = vertex!([10.0, 10.0]);
let vertex_points: Vec<Point<f64, 2>> = simplex
.vertices()
.iter()
.map(|vk| *dt.tds().vertex(*vk).unwrap().point())
.collect();
let result = insphere(&vertex_points, *vertex_far_outside.point());
assert!(result.is_ok());
let center: Vertex<f64, (), 2> = vertex!([0.33, 0.33]);
let vertex_points: Vec<Point<f64, 2>> = simplex
.vertices()
.iter()
.map(|vk| *dt.tds().vertex(*vk).unwrap().point())
.collect();
let result_center = insphere(&vertex_points, *center.point());
assert!(result_center.is_ok());
}
#[test]
fn simplex_circumradius_with_center() {
let vertex1 = vertex!([0.0, 0.0, 0.0]);
let vertex2 = vertex!([1.0, 0.0, 0.0]);
let vertex3 = vertex!([0.0, 1.0, 0.0]);
let vertex4 = vertex!([0.0, 0.0, 1.0]);
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_points: Vec<Point<f64, 3>> = simplex
.vertices()
.iter()
.map(|vk| *dt.tds().vertex(*vk).unwrap().point())
.collect();
let circumcenter = circumcenter(&vertex_points).unwrap();
let radius_with_center = circumradius_with_center(&vertex_points, &circumcenter);
let radius_direct = circumradius(&vertex_points).unwrap();
assert_relative_eq!(radius_with_center.unwrap(), radius_direct, epsilon = 1e-10);
}
#[test]
fn simplex_facet_views_comprehensive() {
let vertex1 = vertex!([0.0, 0.0, 0.0]);
let vertex2 = vertex!([1.0, 0.0, 0.0]);
let vertex3 = vertex!([0.0, 1.0, 0.0]);
let vertex4 = vertex!([0.0, 0.0, 1.0]);
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet_views = Simplex::facet_views_from_tds(dt.tds(), simplex_key)
.expect("Failed to get facet views");
assert_eq!(facet_views.len(), 4, "3D simplex should have 4 facets");
for (i, facet_view) in facet_views.iter().enumerate() {
let facet_vertices = facet_view.vertices().expect("Failed to get facet vertices");
assert_eq!(
facet_vertices.count(),
3,
"Facet {i} should have 3 vertices"
);
}
let simplex = dt.tds().simplex(simplex_key).unwrap();
for (i, facet_view) in facet_views.iter().enumerate() {
let opposite_vertex = facet_view
.opposite_vertex()
.expect("Failed to get opposite vertex");
let opposite_key = dt
.tds()
.vertex_key_from_uuid(&opposite_vertex.uuid())
.unwrap();
assert!(
simplex.vertices().contains(&opposite_key),
"Facet {i} opposite vertex key should be in simplex"
);
}
}
#[test]
fn test_facet_vertex_uniqueness() {
let vertex1 = vertex!([0.0, 0.0, 1.0]);
let vertex2 = vertex!([0.0, 1.0, 0.0]);
let vertex3 = vertex!([1.0, 0.0, 0.0]);
let vertex4 = vertex!([1.0, 1.0, 1.0]);
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet_views = Simplex::facet_views_from_tds(dt.tds(), simplex_key)
.expect("Failed to get facet views");
for facet_view in &facet_views {
let opposite_vertex = facet_view
.opposite_vertex()
.expect("Failed to get opposite vertex");
let opposite_vertex_key = dt
.tds()
.vertex_key_from_uuid(&opposite_vertex.uuid())
.unwrap();
let facet_vertices = facet_view.vertices().expect("Failed to get facet vertices");
let facet_vertex_keys: Vec<_> = facet_vertices
.map(|v| dt.tds().vertex_key_from_uuid(&v.uuid()).unwrap())
.collect();
assert!(
!facet_vertex_keys.contains(&opposite_vertex_key),
"Facet vertices should not include the opposite vertex key"
);
let unique_count = facet_vertex_keys.iter().collect::<HashSet<_>>().len();
assert_eq!(
unique_count,
facet_vertex_keys.len(),
"All facet vertices should be unique"
);
}
}
#[test]
fn simplex_different_numeric_types() {
let vertex1_f32 = vertex!([0.0f32, 0.0f32]);
let vertex2_f32 = vertex!([1.0f32, 0.0f32]);
let vertex3_f32 = vertex!([0.0f32, 1.0f32]);
let dt_f32 = DelaunayTriangulation::new(&[vertex1_f32, vertex2_f32, vertex3_f32]).unwrap();
let (_, simplex_f32) = dt_f32.simplices().next().unwrap();
assert_eq!(simplex_f32.number_of_vertices(), 3);
assert_eq!(simplex_f32.dim(), 2);
}
#[test]
fn simplex_high_dimensional() {
let vertex1 = vertex!([0.0, 0.0, 0.0, 0.0, 0.0]);
let vertex2 = vertex!([1.0, 0.0, 0.0, 0.0, 0.0]);
let vertex3 = vertex!([0.0, 1.0, 0.0, 0.0, 0.0]);
let vertex4 = vertex!([0.0, 0.0, 1.0, 0.0, 0.0]);
let vertex5 = vertex!([0.0, 0.0, 0.0, 1.0, 0.0]);
let vertex6 = vertex!([0.0, 0.0, 0.0, 0.0, 1.0]);
let vertices = vec![vertex1, vertex2, vertex3, vertex4, vertex5, vertex6];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
assert_eq!(simplex.number_of_vertices(), 6);
assert_eq!(simplex.dim(), 5);
assert_eq!(
Simplex::facet_views_from_tds(dt.tds(), simplex_key)
.expect("Failed to get facets")
.len(),
6
); }
#[test]
fn simplex_vertex_data_consistency() {
let vertex1 = vertex!([0.0, 0.0, 0.0], 1);
let vertex2 = vertex!([1.0, 0.0, 0.0], 2);
let vertex3 = vertex!([0.0, 1.0, 0.0], 3);
let vertex4 = vertex!([0.0, 0.0, 1.0], 4);
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let mut dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, u32, 3> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
if let Some(simplex) = dt.tri.tds.simplex_mut(simplex_key) {
simplex.data = Some(42u32);
}
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_data: Vec<i32> = simplex
.vertices()
.iter()
.map(|&vkey| dt.tds().vertex(vkey).unwrap().data.unwrap())
.collect();
for expected in 1..=4 {
assert!(
vertex_data.contains(&expected),
"Expected vertex data {expected} not found"
);
}
assert_eq!(simplex.data.unwrap(), 42u32);
let facet_views = Simplex::facet_views_from_tds(dt.tds(), simplex_key)
.expect("Failed to get facet views");
for facet_view in &facet_views {
let vertices = facet_view.vertices().expect("Failed to get facet vertices");
for vertex in vertices {
assert!(vertex.data.is_some(), "Vertex data should be set");
let data = vertex.data.unwrap();
assert!(
(1..=4).contains(&data),
"Vertex data should be in range 1-4"
);
}
}
}
#[test]
fn simplex_validation_success_cases() {
let vertices_3d = vec![
vertex!([0.0, 0.0, 1.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 0.0]),
];
let dt = DelaunayTriangulation::new(&vertices_3d).unwrap();
let (_, simplex_3d) = dt.simplices().next().unwrap();
assert!(
simplex_3d.is_valid().is_ok(),
"Valid 3D simplex should pass validation"
);
let vertices_2d = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices_2d).unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex_2d = simplex_ref.clone();
assert!(
simplex_2d.is_valid().is_ok(),
"Valid 2D simplex should pass validation"
);
simplex_2d.neighbors = None;
assert!(
simplex_2d.is_valid().is_ok(),
"Simplex with no neighbors should be valid"
);
simplex_2d
.set_neighbors_from_keys(vec![None, None, None])
.unwrap();
assert!(
simplex_2d.is_valid().is_ok(),
"Simplex with correct neighbors length should be valid"
);
}
#[test]
fn simplex_validation_error_cases() {
let vertices = vec![
vertex!([0.0, 0.0, 1.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 0.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut invalid_uuid_simplex = simplex_ref.clone();
invalid_uuid_simplex.uuid = uuid::Uuid::nil();
assert!(
matches!(
invalid_uuid_simplex.is_valid(),
Err(SimplexValidationError::InvalidUuid { .. })
),
"Nil UUID should fail validation"
);
let insufficient_vertices = vec![vertex!([0.0, 0.0, 1.0]), vertex!([0.0, 1.0, 0.0])];
let result = DelaunayTriangulation::new(&insufficient_vertices);
assert!(
result.is_err(),
"TDS should fail with insufficient vertices"
);
let vertices_2d = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices_2d).unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex_wrong_neighbors = simplex_ref.clone();
let err = simplex_wrong_neighbors
.set_neighbors_from_keys(vec![None, None])
.unwrap_err();
assert!(
matches!(
err,
SimplexValidationError::InvalidNeighborsLength {
actual: 2,
expected: 3,
dimension: 2
}
),
"Wrong neighbors count should fail validation"
);
let err = simplex_wrong_neighbors
.set_neighbors_from_keys(vec![None, None, None, None])
.unwrap_err();
assert!(
matches!(
err,
SimplexValidationError::InvalidNeighborsLength {
actual: 4,
expected: 3,
dimension: 2
}
),
"Wrong neighbors count should fail validation"
);
}
#[test]
fn simplex_new_rejects_insufficient_and_duplicate_vertices() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let vkeys: Vec<_> = dt.tds().vertices().map(|(k, _)| k).collect();
let err =
Simplex::<f64, (), (), 3>::new(vec![vkeys[0], vkeys[1], vkeys[2]], None).unwrap_err();
assert!(matches!(
err,
SimplexValidationError::InsufficientVertices {
actual: 3,
expected: 4,
dimension: 3,
}
));
let err =
Simplex::<f64, (), (), 3>::new(vec![vkeys[0], vkeys[1], vkeys[2], vkeys[0]], None)
.unwrap_err();
assert!(matches!(err, SimplexValidationError::DuplicateVertices));
}
#[test]
fn simplex_is_valid_rejects_insufficient_and_duplicate_vertices() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut wrong_len = simplex_ref.clone();
wrong_len.vertices.pop();
assert!(matches!(
wrong_len.is_valid(),
Err(SimplexValidationError::InsufficientVertices { .. })
));
let mut dup = simplex_ref.clone();
dup.vertices[1] = dup.vertices[0];
assert!(matches!(
dup.is_valid(),
Err(SimplexValidationError::DuplicateVertices)
));
}
#[test]
fn simplex_ensure_neighbors_buffer_mut_initializes_and_reuses() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (simplex_key, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex = simplex_ref.clone();
simplex.clear_neighbors();
assert!(simplex.neighbors.is_none());
let buf = simplex.ensure_neighbors_buffer_mut();
assert_eq!(buf.len(), 3);
assert!(buf.iter().all(|slot| slot.is_unassigned()));
buf[0] = NeighborSlot::Neighbor(simplex_key);
let buf2 = simplex.ensure_neighbors_buffer_mut();
assert_eq!(buf2[0], NeighborSlot::Neighbor(simplex_key));
}
#[test]
fn simplex_neighbor_views_distinguish_unassigned_boundary_and_neighbor_slots() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (simplex_key, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex = simplex_ref.clone();
simplex.clear_neighbors();
assert!(simplex.neighbor_slots().is_none());
assert!(simplex.neighbors().is_none());
simplex
.set_neighbors_from_keys([None, Some(simplex_key), None])
.unwrap();
let slots = simplex
.neighbor_slots()
.expect("assigned slots should exist");
assert_eq!(slots.len(), 3);
assert_eq!(slots[0], NeighborSlot::Boundary);
assert_eq!(slots[1], NeighborSlot::Neighbor(simplex_key));
assert_eq!(slots[2], NeighborSlot::Boundary);
let neighbor_keys: Vec<_> = simplex
.neighbors()
.expect("neighbor iterator should exist")
.collect();
assert_eq!(neighbor_keys, &[None, Some(simplex_key), None]);
}
#[test]
fn simplex_validation_rejects_unassigned_slot_inside_assigned_neighbors() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex = simplex_ref.clone();
let slots = simplex.ensure_neighbors_buffer_mut();
slots[0] = NeighborSlot::Unassigned;
assert!(matches!(
simplex.is_valid(),
Err(SimplexValidationError::UnassignedNeighborSlot { facet_index: 0 })
));
}
#[test]
fn simplex_swap_vertex_slots_swaps_vertices_neighbors_and_offsets() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (simplex_key, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex = simplex_ref.clone();
simplex
.set_neighbors_from_keys(vec![Some(simplex_key), None, Some(simplex_key)])
.unwrap();
simplex
.set_periodic_vertex_offsets(vec![[1, 0], [2, 0], [3, 0]])
.unwrap();
let before_vertices = simplex.vertices().to_vec();
let before_neighbors: Vec<_> = simplex.neighbors().unwrap().collect();
let before_offsets = simplex.periodic_vertex_offsets().unwrap().to_vec();
simplex.swap_vertex_slots(0, 2);
assert_eq!(simplex.vertices()[0], before_vertices[2]);
assert_eq!(simplex.vertices()[2], before_vertices[0]);
assert_eq!(simplex.neighbor_key(0).flatten(), before_neighbors[2]);
assert_eq!(simplex.neighbor_key(2).flatten(), before_neighbors[0]);
let offsets = simplex.periodic_vertex_offsets().unwrap();
assert_eq!(offsets[0], before_offsets[2]);
assert_eq!(offsets[2], before_offsets[0]);
}
#[test]
#[should_panic(expected = "neighbors index out of bounds")]
fn simplex_swap_vertex_slots_panics_when_neighbors_shorter_than_vertices() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex = simplex_ref.clone();
let mut neighbors = NeighborBuffer::<NeighborSlot>::new();
neighbors.resize(2, NeighborSlot::Boundary);
simplex.neighbors = Some(neighbors);
simplex.swap_vertex_slots(0, 2);
}
#[test]
fn simplex_facet_view_helpers_reject_excessive_vertex_count() {
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::new(&vertices).unwrap();
let simplex_key = dt.tds().simplex_keys().next().unwrap();
let vkey0 = {
let simplex = dt.tds().simplex(simplex_key).unwrap();
simplex.vertices()[0]
};
{
let simplex = dt
.tri
.tds
.simplex_mut(simplex_key)
.expect("simplex key should be valid in test");
while u8::try_from(simplex.number_of_vertices()).is_ok() {
simplex.push_vertex_key(vkey0);
}
assert!(u8::try_from(simplex.number_of_vertices()).is_err());
}
let err = Simplex::facet_views_from_tds(dt.tds(), simplex_key).unwrap_err();
assert!(matches!(
err,
FacetError::InvalidFacetIndex {
index: u8::MAX,
facet_count,
} if u8::try_from(facet_count).is_err()
));
let err = Simplex::facet_view_iter(dt.tds(), simplex_key)
.err()
.expect("Expected facet_view_iter to fail on vertex_count overflow");
assert!(matches!(
err,
FacetError::InvalidFacetIndex {
index: u8::MAX,
facet_count,
} if u8::try_from(facet_count).is_err()
));
}
#[test]
fn simplex_deserialize_rejects_missing_uuid_and_duplicate_fields_and_invalid_uuid() {
let err = serde_json::from_str::<Simplex<f64, (), i32, 3>>("null").unwrap_err();
assert!(err.to_string().contains("a Simplex struct"));
let err = serde_json::from_str::<Simplex<f64, (), i32, 3>>("{\"data\":1}").unwrap_err();
assert!(err.to_string().contains("missing field `uuid`"));
let uuid = uuid::Uuid::new_v4();
let json = format!("{{\"uuid\":\"{uuid}\",\"uuid\":\"{uuid}\"}}");
let err = serde_json::from_str::<Simplex<f64, (), i32, 3>>(&json).unwrap_err();
assert!(err.to_string().contains("duplicate field `uuid`"));
let uuid = uuid::Uuid::new_v4();
let json = format!("{{\"uuid\":\"{uuid}\",\"data\":1,\"data\":2}}");
let err = serde_json::from_str::<Simplex<f64, (), i32, 3>>(&json).unwrap_err();
assert!(err.to_string().contains("duplicate field `data`"));
let json = "{\"uuid\":\"00000000-0000-0000-0000-000000000000\"}";
let err = serde_json::from_str::<Simplex<f64, (), i32, 3>>(json).unwrap_err();
assert!(err.to_string().contains("invalid uuid"));
let uuid = uuid::Uuid::new_v4();
let json = format!("{{\"uuid\":\"{uuid}\",\"data\":5,\"neighbors\":[1,2,3]}}");
let simplex = serde_json::from_str::<Simplex<f64, (), i32, 3>>(&json).unwrap();
assert_eq!(simplex.data, Some(5));
assert!(simplex.neighbors.is_none());
assert_eq!(
simplex.number_of_vertices(),
0,
"vertices are not serialized"
);
}
#[test]
fn simplex_validation_error_from_stack_matrix_dispatch_error_maps_to_coordinate_conversion() {
let err = StackMatrixDispatchError::UnsupportedDim {
k: MAX_STACK_MATRIX_DIM + 1,
max: MAX_STACK_MATRIX_DIM,
};
let simplex_err: SimplexValidationError = err.into();
assert!(matches!(
simplex_err,
SimplexValidationError::CoordinateConversion { .. }
));
}
#[test]
fn test_simplex_partial_eq_different_dimensions() {
let vertices_2d = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt1 = DelaunayTriangulation::new(&vertices_2d).unwrap();
let (_, simplex_2d) = dt1.tds().simplices().next().unwrap();
let dt2 = DelaunayTriangulation::new(&vertices_2d).unwrap();
let (_, simplex_2d_copy) = dt2.tds().simplices().next().unwrap();
assert_eq!(
simplex_2d, simplex_2d_copy,
"Identical 2D simplices should be equal"
);
println!("✓ 2D simplices work correctly with PartialEq");
}
#[test]
fn test_facet_view_iter() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let simplex_key = dt.tds().simplex_keys().next().unwrap();
let facet_iter =
Simplex::facet_view_iter(dt.tds(), simplex_key).expect("Failed to get facet iterator");
assert_eq!(
facet_iter.len(),
4,
"Should have 4 facets for a tetrahedron"
);
let facet_results: Vec<_> = facet_iter.collect();
assert_eq!(facet_results.len(), 4);
for (i, facet_result) in facet_results.iter().enumerate() {
let facet_view = facet_result
.as_ref()
.unwrap_or_else(|_| panic!("Facet {i} creation should succeed"));
let vertex_count = facet_view.vertices().unwrap().count();
assert_eq!(vertex_count, 3, "Facet {i} should have 3 vertices");
}
let facet_iter2 = Simplex::facet_view_iter(dt.tds(), simplex_key)
.expect("Failed to get second facet iterator");
let mut count = 0;
for facet_result in facet_iter2 {
let _facet_view = facet_result.expect("Facet creation should succeed");
count += 1;
}
assert_eq!(count, 4, "Iterator should yield 4 facets");
let facet_iter3 = Simplex::facet_view_iter(dt.tds(), simplex_key)
.expect("Failed to get third facet iterator");
let successful_facets: Vec<_> = facet_iter3
.collect::<Result<Vec<_>, _>>()
.expect("all facets should be created successfully");
assert_eq!(
successful_facets.len(),
4,
"All facets should be created successfully"
);
let vec_facets = Simplex::facet_views_from_tds(dt.tds(), simplex_key)
.expect("Vec-based method should work");
assert_eq!(
successful_facets.len(),
vec_facets.len(),
"Iterator and Vec methods should return same count"
);
println!("✓ facet_view_iter zero-allocation iterator works correctly");
}
#[test]
fn test_simplex_data_accessor() {
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 key = dt.simplices().next().unwrap().0;
assert_eq!(dt.tds().simplex(key).unwrap().data(), None);
dt.set_simplex_data(key, Some(99));
assert_eq!(dt.tds().simplex(key).unwrap().data(), Some(&99));
}
#[test]
fn test_clear_vertex_keys() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_simplex_key, original_simplex) = dt.simplices().next().unwrap();
let mut test_simplex = original_simplex.clone();
assert_eq!(
test_simplex.number_of_vertices(),
4,
"Simplex should start with 4 vertices"
);
test_simplex.clear_vertex_keys();
assert_eq!(
test_simplex.number_of_vertices(),
0,
"Simplex should have 0 vertices after clearing"
);
assert_eq!(
test_simplex.vertices().len(),
0,
"Vertices slice should be empty after clearing"
);
for &vkey in original_simplex.vertices() {
test_simplex.push_vertex_key(vkey);
}
assert_eq!(
test_simplex.number_of_vertices(),
4,
"Simplex should have 4 vertices after rebuilding"
);
for (original_vkey, rebuilt_vkey) in original_simplex
.vertices()
.iter()
.zip(test_simplex.vertices().iter())
{
assert_eq!(
original_vkey, rebuilt_vkey,
"Rebuilt vertex keys should match original keys"
);
}
println!("✓ clear_vertex_keys() correctly clears and allows rebuilding of vertex keys");
}
}