#![allow(clippy::similar_names)]
#![forbid(unsafe_code)]
use super::vertex::{Vertex, VertexValidationError};
use super::{
tds::{EntityKind, SimplexKey, Tds, TdsConstructionError, VertexKey},
traits::{DataDeserialize, DataSerialize},
util::{UuidValidationError, make_uuid, validate_uuid},
};
use crate::core::collections::{
FastHashMap, NeighborBuffer, PeriodicOffsetBuffer, SimplexVertexBuffer, SimplexVertexKeyBuffer,
SimplexVertexUuidBuffer, fast_hash_map_with_capacity,
};
use crate::geometry::matrix::StackMatrixDispatchError;
use crate::geometry::traits::coordinate::CoordinateConversionError;
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)]
#[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 vertex identities")]
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,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct SimplexValidationReport {
pub violations: Vec<SimplexValidationError>,
}
impl SimplexValidationReport {
#[must_use]
pub const fn is_empty(&self) -> bool {
self.violations.is_empty()
}
#[must_use]
pub fn violations(&self) -> &[SimplexValidationError] {
&self.violations
}
}
impl From<StackMatrixDispatchError> for SimplexValidationError {
fn from(source: StackMatrixDispatchError) -> Self {
CoordinateConversionError::from(source).into()
}
}
fn compare_vertices_by_coordinates<U, const D: usize>(
left: &Vertex<U, D>,
right: &Vertex<U, D>,
) -> cmp::Ordering {
for (left_coord, right_coord) in left.point().coords().iter().zip(right.point().coords()) {
let ordering = left_coord.total_cmp(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<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>>,
}
impl<V, const D: usize> Serialize for Simplex<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()
}
}
struct StandaloneSimplexRecord<V> {
uuid: Uuid,
data: Option<V>,
}
impl<'de, V> Deserialize<'de> for StandaloneSimplexRecord<V>
where
V: DataDeserialize,
{
fn deserialize<De>(deserializer: De) -> Result<Self, De::Error>
where
De: Deserializer<'de>,
{
struct StandaloneSimplexVisitor<V>
where
V: DataDeserialize,
{
_phantom: PhantomData<V>,
}
impl<'de, V> Visitor<'de> for StandaloneSimplexVisitor<V>
where
V: DataDeserialize,
{
type Value = StandaloneSimplexRecord<V>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a standalone Simplex record")
}
fn visit_map<A>(self, mut map: A) -> Result<StandaloneSimplexRecord<V>, A::Error>
where
A: MapAccess<'de>,
{
let mut uuid = None;
let mut data = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"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()?);
}
"vertices" | "neighbors" | "periodic_vertex_offsets" => {
return Err(de::Error::custom(format!(
"{key} is storage-local simplex state and must not be deserialized; deserialize Tds so UUID relationships can be reconstructed",
)));
}
_ => {
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}")))?;
Ok(StandaloneSimplexRecord {
uuid,
data: data.flatten(),
})
}
}
const FIELDS: &[&str] = &["uuid", "data", "vertices"];
deserializer.deserialize_struct(
"StandaloneSimplex",
FIELDS,
StandaloneSimplexVisitor {
_phantom: PhantomData,
},
)
}
}
impl<'de, V, const D: usize> Deserialize<'de> for Simplex<V, D>
where
V: DataDeserialize,
{
fn deserialize<De>(deserializer: De) -> Result<Self, De::Error>
where
De: Deserializer<'de>,
{
struct SimplexVisitor<V, const D: usize>
where
V: DataDeserialize,
{
_phantom: PhantomData<V>,
}
impl<'de, V, const D: usize> Visitor<'de> for SimplexVisitor<V, D>
where
V: DataDeserialize,
{
type Value = Simplex<V, D>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a Simplex struct")
}
fn visit_map<A>(self, map: A) -> Result<Simplex<V, D>, A::Error>
where
A: MapAccess<'de>,
{
let StandaloneSimplexRecord { uuid, data } =
StandaloneSimplexRecord::<V>::deserialize(
de::value::MapAccessDeserializer::new(map),
)?;
let _ = (uuid, data);
Err(de::Error::custom(
"standalone Simplex deserialization is unsupported; deserialize Tds so simplex vertex UUIDs can be resolved to live keys",
))
}
}
const FIELDS: &[&str] = &["uuid", "data"];
deserializer.deserialize_struct(
"Simplex",
FIELDS,
SimplexVisitor {
_phantom: PhantomData,
},
)
}
}
impl<V, const D: usize> Simplex<V, D> {
pub(crate) fn try_new(
vertices: impl Into<SimplexVertexKeyBuffer>,
) -> Result<Self, SimplexValidationError> {
Self::try_new_with_data(vertices, None)
}
pub(crate) fn try_new_with_data(
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,
});
}
for (index, &vkey) in vertices.iter().enumerate() {
if vertices[..index].contains(&vkey) {
return Err(SimplexValidationError::DuplicateVertices);
}
}
Ok(Self {
vertices,
uuid: make_uuid(),
neighbors: None,
data,
periodic_vertex_offsets: None,
})
}
pub(crate) fn try_new_periodic(
vertices: impl Into<SimplexVertexKeyBuffer>,
offsets: impl Into<PeriodicOffsetBuffer<D>>,
) -> Result<Self, SimplexValidationError> {
Self::try_new_periodic_with_uuid(vertices, offsets, make_uuid(), None)
}
pub(crate) fn try_new_periodic_with_uuid(
vertices: impl Into<SimplexVertexKeyBuffer>,
offsets: impl Into<PeriodicOffsetBuffer<D>>,
uuid: Uuid,
data: Option<V>,
) -> Result<Self, SimplexValidationError> {
validate_uuid(&uuid)?;
let vertices = vertices.into();
let offsets = offsets.into();
let actual = vertices.len();
if actual != D + 1 {
return Err(SimplexValidationError::InsufficientVertices {
actual,
expected: D + 1,
dimension: D,
});
}
if offsets.len() != vertices.len() {
return Err(SimplexValidationError::PeriodicOffsetLengthMismatch {
expected: vertices.len(),
found: offsets.len(),
});
}
for index in 0..vertices.len() {
if (0..index).any(|earlier| {
vertices[earlier] == vertices[index] && offsets[earlier] == offsets[index]
}) {
return Err(SimplexValidationError::DuplicateVertices);
}
}
Ok(Self {
vertices,
uuid,
neighbors: None,
data,
periodic_vertex_offsets: Some(offsets),
})
}
pub(crate) fn try_new_with_uuid(
vertices: impl Into<SimplexVertexKeyBuffer>,
uuid: Uuid,
data: Option<V>,
) -> Result<Self, SimplexValidationError> {
validate_uuid(&uuid)?;
let mut simplex = Self::try_new_with_data(vertices, data)?;
simplex.uuid = uuid;
Ok(simplex)
}
#[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]
#[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;
}
if self.vertices().len() != neighbor_simplex.vertices().len() {
return None;
}
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
}
#[cfg(test)]
#[inline]
pub(crate) fn push_vertex_key(&mut self, vertex_key: VertexKey) {
self.vertices.push(vertex_key);
self.periodic_vertex_offsets = None;
}
#[cfg(test)]
#[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> {
self.neighbors.get_or_insert_with(|| {
let mut buffer = NeighborBuffer::new();
buffer.resize(D + 1, NeighborSlot::Unassigned);
buffer
})
}
#[inline]
pub(crate) fn try_ensure_neighbors_buffer_mut(
&mut self,
) -> Result<&mut NeighborBuffer<NeighborSlot>, SimplexValidationError> {
let buffer = self.ensure_neighbors_buffer_mut();
if buffer.len() != D + 1 {
return Err(SimplexValidationError::InvalidNeighborsLength {
actual: buffer.len(),
expected: D + 1,
dimension: D,
});
}
Ok(buffer)
}
}
impl<V, const D: usize> Simplex<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)]
#[inline]
pub(crate) fn clear_neighbors(&mut self) {
self.neighbors = None;
}
#[inline]
pub fn vertex_uuids<U>(
&self,
tds: &Tds<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, U>(
&'a self,
tds: &'a Tds<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
}
pub fn try_into_hashmap<I>(
simplices: I,
) -> Result<FastHashMap<Uuid, Self>, TdsConstructionError>
where
I: IntoIterator<Item = Self>,
{
let iter = simplices.into_iter();
let mut map = fast_hash_map_with_capacity(iter.size_hint().0);
for simplex in iter {
let uuid = simplex.uuid();
if map.insert(uuid, simplex).is_some() {
return Err(TdsConstructionError::DuplicateUuid {
entity: EntityKind::Simplex,
uuid,
});
}
}
Ok(map)
}
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,
});
}
if let Some(offsets) = &self.periodic_vertex_offsets
&& offsets.len() != self.vertices.len()
{
return Err(SimplexValidationError::PeriodicOffsetLengthMismatch {
expected: self.vertices.len(),
found: offsets.len(),
});
}
for index in 0..self.vertices.len() {
let is_duplicate = (0..index).any(|earlier| {
self.vertices[earlier] == self.vertices[index]
&& self
.periodic_vertex_offsets
.as_ref()
.is_none_or(|offsets| offsets[earlier] == offsets[index])
});
if is_duplicate {
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(())
}
#[must_use]
pub fn simplex_diagnostic(&self) -> Option<SimplexValidationError> {
self.is_valid().err()
}
pub fn simplex_report(&self) -> Result<(), SimplexValidationReport> {
let mut violations = Vec::new();
if let Err(source) = validate_uuid(&self.uuid) {
violations.push(SimplexValidationError::InvalidUuid { source });
}
if self.vertices.len() != D + 1 {
violations.push(SimplexValidationError::InsufficientVertices {
actual: self.vertices.len(),
expected: D + 1,
dimension: D,
});
}
let periodic_offsets_valid = self.periodic_vertex_offsets.as_ref().is_none_or(|offsets| {
if offsets.len() == self.vertices.len() {
true
} else {
violations.push(SimplexValidationError::PeriodicOffsetLengthMismatch {
expected: self.vertices.len(),
found: offsets.len(),
});
false
}
});
let mut duplicate_vertices = false;
if periodic_offsets_valid {
for index in 0..self.vertices.len() {
if (0..index).any(|earlier| {
self.vertices[earlier] == self.vertices[index]
&& self
.periodic_vertex_offsets
.as_ref()
.is_none_or(|offsets| offsets[earlier] == offsets[index])
}) {
duplicate_vertices = true;
break;
}
}
}
if duplicate_vertices {
violations.push(SimplexValidationError::DuplicateVertices);
}
if let Some(ref neighbors) = self.neighbors {
if neighbors.len() != D + 1 {
violations.push(SimplexValidationError::InvalidNeighborsLength {
actual: neighbors.len(),
expected: D + 1,
dimension: D,
});
}
for (facet_index, slot) in neighbors.iter().enumerate() {
if slot.is_unassigned() {
violations.push(SimplexValidationError::UnassignedNeighborSlot { facet_index });
}
}
}
if violations.is_empty() {
Ok(())
} else {
Err(SimplexValidationReport { violations })
}
}
}
impl<V, const D: usize> Simplex<V, D> {
pub fn eq_by_vertices<U>(
&self,
self_tds: &Tds<U, V, D>,
other: &Self,
other_tds: &Tds<U, V, D>,
) -> bool {
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
}
}
type SimplexIdentitySortKey<const D: usize> =
(SimplexVertexBuffer<(VertexKey, [i8; D])>, Option<usize>);
fn canonical_simplex_identity<V, const D: usize>(
simplex: &Simplex<V, D>,
) -> SimplexIdentitySortKey<D> {
let periodic_offsets = simplex.periodic_vertex_offsets.as_deref();
let mut identities: SimplexVertexBuffer<(VertexKey, [i8; D])> = simplex
.vertices
.iter()
.copied()
.enumerate()
.map(|(index, key)| {
let offset = periodic_offsets
.and_then(|offsets| offsets.get(index))
.copied()
.unwrap_or([0_i8; D]);
(key, offset)
})
.collect();
identities.sort_unstable();
let malformed_offset_len = periodic_offsets
.and_then(|offsets| (offsets.len() != simplex.vertices.len()).then_some(offsets.len()));
(identities, malformed_offset_len)
}
impl<V, const D: usize> PartialEq for Simplex<V, D> {
#[inline]
fn eq(&self, other: &Self) -> bool {
canonical_simplex_identity(self) == canonical_simplex_identity(other)
}
}
impl<V, const D: usize> PartialOrd for Simplex<V, D> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
canonical_simplex_identity(self).partial_cmp(&canonical_simplex_identity(other))
}
}
impl<V, const D: usize> Eq for Simplex<V, D> {}
impl<V, const D: usize> Hash for Simplex<V, D> {
fn hash<H: Hasher>(&self, state: &mut H) {
canonical_simplex_identity(self).hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::builder::DelaunayTriangulationBuilder;
use crate::core::collections::FastHashSet;
use crate::core::facet::FacetError;
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 crate::vertex;
use approx::assert_relative_eq;
use proptest::prelude::*;
use std::assert_matches;
use std::iter::once;
use std::{
cmp,
collections::{HashSet, hash_map::DefaultHasher},
hash::Hasher,
};
type TestVertex3D = Vertex<(), 3>;
type TestVertex2D = Vertex<(), 2>;
struct NonDataType(String);
fn simplex_with_non_data_type_metadata<const D: usize>(
vertex_ids: [u64; D],
data: NonDataType,
) -> Simplex<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,
}
}
fn synthetic_vertex_keys(count: usize) -> Vec<VertexKey> {
(0..count)
.map(|index| {
let key_value = u64::try_from(index + 1).expect("test key index fits in u64");
VertexKey::from(slotmap::KeyData::from_ffi(key_value))
})
.collect()
}
fn simplex_with_periodic_offsets<const D: usize>(offsets: &[[i8; D]]) -> Simplex<(), D> {
assert_eq!(offsets.len(), D + 1);
let vertex_keys = synthetic_vertex_keys(D + 1);
let mut simplex = Simplex::try_new(vertex_keys).expect("synthetic keys are distinct");
simplex
.set_periodic_vertex_offsets(offsets.to_vec())
.expect("one offset per simplex vertex");
simplex
}
macro_rules! gen_periodic_offset_slot_tests {
($dim:literal, $uniform:path) => {
pastey::paste! {
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn [<prop_periodic_offsets_follow_slot_swaps_ $dim d>](
offsets in prop::collection::vec(
$uniform(any::<i8>()),
($dim + 1)..=($dim + 1),
),
swaps in prop::collection::vec(
(0_usize..($dim + 1), 0_usize..($dim + 1)),
0..=32,
),
) {
let mut simplex = simplex_with_periodic_offsets::<$dim>(&offsets);
let mut expected: Vec<_> = simplex
.vertices()
.iter()
.copied()
.zip(offsets.iter().copied())
.collect();
for (first, second) in swaps {
simplex.swap_vertex_slots(first, second);
expected.swap(first, second);
}
let actual: Vec<_> = simplex
.vertices()
.iter()
.copied()
.zip(
simplex
.periodic_vertex_offsets()
.expect("offsets remain present")
.iter()
.copied(),
)
.collect();
prop_assert_eq!(actual, expected);
prop_assert!(simplex.is_valid().is_ok());
}
}
#[test]
fn [<test_periodic_offset_length_failure_is_atomic_ $dim d>]() {
let valid_offsets = vec![[0_i8; $dim]; $dim + 1];
let mut simplex = simplex_with_periodic_offsets::<$dim>(&valid_offsets);
let before = simplex
.periodic_vertex_offsets()
.expect("fixture offsets are present")
.to_vec();
for found in [0_usize, $dim, $dim + 2] {
let error = simplex
.set_periodic_vertex_offsets(vec![[1_i8; $dim]; found])
.expect_err("misaligned periodic offsets should be rejected");
assert_eq!(
error,
SimplexValidationError::PeriodicOffsetLengthMismatch {
expected: $dim + 1,
found,
}
);
assert_eq!(
simplex.periodic_vertex_offsets(),
Some(before.as_slice()),
"failed setter must preserve the prior aligned offsets",
);
}
}
}
};
}
gen_periodic_offset_slot_tests!(2, prop::array::uniform2);
gen_periodic_offset_slot_tests!(3, prop::array::uniform3);
gen_periodic_offset_slot_tests!(4, prop::array::uniform4);
gen_periodic_offset_slot_tests!(5, prop::array::uniform5);
macro_rules! test_simplex_dimensions {
($(
$test_name:ident => $dim:expr => $vertices:expr
),+ $(,)?) => {
$(
#[test]
fn $test_name() {
let vertices = $vertices;
let dt = DelaunayTriangulation::builder(&vertices).build().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> =
DelaunayTriangulationBuilder::new(&vertices)
.simplex_data_type::<i32>()
.build()
.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> =
DelaunayTriangulationBuilder::new(&vertices)
.simplex_data_type::<i32>()
.build()
.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 err = serde_json::from_str::<Simplex<i32, $dim>>(&serialized)
.expect_err("standalone Simplex deserialization should require TDS UUID mapping");
assert!(err.to_string().contains("standalone Simplex deserialization"));
let vertices = $vertices;
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let (_, simplex) = dt.simplices().next().unwrap();
let serialized = serde_json::to_string(&simplex).unwrap();
assert!(!serialized.contains("\"data\":"));
let err = serde_json::from_str::<Simplex<Option<i32>, $dim>>(&serialized)
.expect_err("standalone Simplex deserialization should require TDS UUID mapping");
assert!(err.to_string().contains("standalone Simplex deserialization"));
}
#[test]
fn [<$test_name _uuid_uniqueness>]() {
let vertices1 = $vertices;
let vertices2 = $vertices;
let dt1 = DelaunayTriangulation::builder(&vertices1).build().unwrap();
let (_, simplex1) = dt1.simplices().next().unwrap();
let dt2 = DelaunayTriangulation::builder(&vertices2).build().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]
fn periodic_equality_ordering_and_hash_include_offsets() {
let keys = synthetic_vertex_keys(3);
let non_periodic = Simplex::<(), 2>::try_new(keys.clone()).unwrap();
let zero_offset_periodic =
Simplex::<(), 2>::try_new_periodic(keys.clone(), vec![[0, 0]; 3]).unwrap();
let simplex =
Simplex::<(), 2>::try_new_periodic(keys.clone(), vec![[0, 0], [1, 0], [0, 1]]).unwrap();
let permuted = Simplex::<(), 2>::try_new_periodic(
vec![keys[2], keys[0], keys[1]],
vec![[0, 1], [0, 0], [1, 0]],
)
.unwrap();
let different_offset =
Simplex::<(), 2>::try_new_periodic(keys, vec![[0, 0], [2, 0], [0, 1]]).unwrap();
assert_eq!(simplex, permuted);
assert_eq!(simplex.partial_cmp(&permuted), Some(cmp::Ordering::Equal));
assert_ne!(simplex, different_offset);
assert_ne!(
simplex.partial_cmp(&different_offset),
Some(cmp::Ordering::Equal)
);
assert_eq!(non_periodic, zero_offset_periodic);
assert_eq!(
non_periodic.partial_cmp(&zero_offset_periodic),
Some(cmp::Ordering::Equal)
);
let mut non_periodic_hash = DefaultHasher::new();
let mut zero_offset_periodic_hash = DefaultHasher::new();
let mut simplex_hash = DefaultHasher::new();
let mut permuted_hash = DefaultHasher::new();
let mut different_offset_hash = DefaultHasher::new();
non_periodic.hash(&mut non_periodic_hash);
zero_offset_periodic.hash(&mut zero_offset_periodic_hash);
simplex.hash(&mut simplex_hash);
permuted.hash(&mut permuted_hash);
different_offset.hash(&mut different_offset_hash);
assert_eq!(
non_periodic_hash.finish(),
zero_offset_periodic_hash.finish()
);
assert_eq!(simplex_hash.finish(), permuted_hash.finish());
assert_ne!(simplex_hash.finish(), different_offset_hash.finish());
}
test_simplex_dimensions! {
simplex_2d => 2 => vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
],
simplex_3d => 3 => vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
],
simplex_4d => 4 => vec![
vertex!([0.0, 0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 0.0, 1.0]).unwrap(),
],
simplex_5d => 5 => vec![
vertex!([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap(),
],
}
fn assert_simplex_properties<V, const D: usize>(
simplex: &Simplex<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]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
]
}
fn create_test_vertices_2d() -> Vec<TestVertex2D> {
vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
]
}
#[test]
fn simplex_from_triangulation_without_data() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(), ];
let dt = DelaunayTriangulation::builder(&vertices).build().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"
);
}
}
#[test]
fn test_eq_by_vertices_same_coordinates_different_tds() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt1 = DelaunayTriangulation::builder(&vertices).build().unwrap();
let dt2 = DelaunayTriangulation::builder(&vertices).build().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()));
}
#[test]
fn test_eq_by_vertices_2d() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt1 = DelaunayTriangulation::builder(&vertices).build().unwrap();
let dt2 = DelaunayTriangulation::builder(&vertices).build().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()));
}
#[test]
fn simplex_from_triangulation_with_data() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, (), i32, 3> =
DelaunayTriangulationBuilder::new(&vertices)
.simplex_data_type::<i32>()
.build()
.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"
);
}
}
#[test]
fn simplex_with_vertex_data() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]; data = 1).unwrap(),
vertex!([1.0, 0.0, 0.0]; data = 2).unwrap(),
vertex!([0.0, 1.0, 0.0]; data = 3).unwrap(),
vertex!([0.0, 0.0, 1.0]; data = 4).unwrap(), ];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, (), 3> =
DelaunayTriangulationBuilder::new(&vertices)
.build()
.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]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 0.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().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]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 1.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&all_vertices)
.build()
.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]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 0.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().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]; data = 1).unwrap(),
vertex!([0.0, 1.0, 0.0]; data = 1).unwrap(),
vertex!([1.0, 0.0, 0.0]; data = 1).unwrap(),
vertex!([1.0, 1.0, 1.0]; data = 2).unwrap(),
];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, i32, 3> =
DelaunayTriangulationBuilder::new(&vertices)
.simplex_data_type::<i32>()
.build()
.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]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let (_, 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::builder(&vertices_2d)
.build()
.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::builder(&vertices_3d)
.build()
.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]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
vertex!([1.0, 1.1]).unwrap(), ];
let dt = DelaunayTriangulation::builder(&vertices).build().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]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
vertex!([1.0, 1.0]).unwrap(),
vertex!([0.5, 0.5]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().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_mirror_facet_index_returns_none_for_mismatched_vertex_counts() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut malformed_neighbor = simplex_ref.clone();
malformed_neighbor.vertices.pop();
assert_eq!(simplex_ref.mirror_facet_index(0, &malformed_neighbor), None);
}
#[test]
fn simplex_dim() {
let vertices_2d = create_test_vertices_2d();
let dt_2d = DelaunayTriangulation::builder(&vertices_2d)
.build()
.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::builder(&vertices_3d)
.build()
.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<i32, 3> = vertex!([0.0, 0.0, 1.0]; data = 1).unwrap();
let vertex2 = vertex!([0.0, 1.0, 0.0]; data = 1).unwrap();
let vertex3 = vertex!([1.0, 0.0, 0.0]; data = 1).unwrap();
let vertex4 = vertex!([1.0, 1.0, 1.0]; data = 2).unwrap();
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, (), 3> =
DelaunayTriangulationBuilder::new(&vertices)
.build()
.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]));
}
#[test]
fn simplex_has_vertex_in_common() {
let vertices1 = vec![
vertex!([0.0, 0.0, 1.0]; data = 1).unwrap(),
vertex!([0.0, 1.0, 0.0]; data = 1).unwrap(),
vertex!([1.0, 0.0, 0.0]; data = 1).unwrap(),
vertex!([1.0, 1.0, 1.0]; data = 2).unwrap(),
];
let tds1: DelaunayTriangulation<AdaptiveKernel<f64>, i32, i32, 3> =
DelaunayTriangulationBuilder::new(&vertices1)
.simplex_data_type::<i32>()
.build()
.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]; data = 1).unwrap(),
vertex!([0.0, 1.0, 0.0]; data = 1).unwrap(),
vertex!([1.0, 0.0, 0.0]; data = 1).unwrap(),
vertex!([0.0, 0.0, 0.0]; data = 0).unwrap(),
];
let tds2: DelaunayTriangulation<AdaptiveKernel<f64>, i32, i32, 3> =
DelaunayTriangulationBuilder::new(&vertices2)
.simplex_data_type::<i32>()
.build()
.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));
}
#[test]
fn test_vertex_uuids_success() {
let vertex1 = vertex!([0.0, 0.0, 0.0]; data = 10).unwrap();
let vertex2 = vertex!([1.0, 0.0, 0.0]; data = 20).unwrap();
let vertex3 = vertex!([0.0, 1.0, 0.0]; data = 30).unwrap();
let vertex4 = vertex!([0.0, 0.0, 1.0]; data = 40).unwrap();
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, (), 3> =
DelaunayTriangulationBuilder::new(&vertices)
.build()
.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());
}
}
#[test]
fn test_vertex_uuids_empty_simplex_fails() {
let vertices = vec![vertex!([0.0, 0.0, 0.0]).unwrap()];
let result = DelaunayTriangulation::builder(&vertices).build();
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("Insufficient vertices"));
}
#[test]
fn test_vertex_uuids_2d_simplex() {
let vertex1 = vertex!([0.0, 0.0]; data = 1).unwrap();
let vertex2 = vertex!([1.0, 0.0]; data = 2).unwrap();
let vertex3 = vertex!([0.5, 1.0]; data = 3).unwrap();
let vertices = vec![vertex1, vertex2, vertex3];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, (), 2> =
DelaunayTriangulationBuilder::new(&vertices)
.build()
.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());
}
}
#[test]
fn test_vertex_uuids_4d_simplex() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0, 0.0]; data = 1).unwrap(),
vertex!([1.0, 0.0, 0.0, 0.0]; data = 2).unwrap(),
vertex!([0.0, 1.0, 0.0, 0.0]; data = 3).unwrap(),
vertex!([0.0, 0.0, 1.0, 0.0]; data = 4).unwrap(),
vertex!([0.0, 0.0, 0.0, 1.0]; data = 5).unwrap(),
];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, (), 4> =
DelaunayTriangulationBuilder::new(&vertices)
.build()
.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());
}
}
#[test]
fn simplex_1d() {
let vertices = vec![vertex!([0.0]).unwrap(), vertex!([1.0]).unwrap()];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let (_, simplex) = dt.simplices().next().unwrap();
assert_simplex_properties(simplex, 2, 1);
}
#[test]
fn simplex_single_vertex() {
let vertices = vec![vertex!([0.0, 0.0, 0.0]).unwrap()];
let result = DelaunayTriangulation::builder(&vertices).build();
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]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let (_, 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]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let (_, 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]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, (), i32, 3> =
DelaunayTriangulationBuilder::new(&vertices)
.simplex_data_type::<i32>()
.build()
.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::<(), 3>::try_into_hashmap([]).unwrap();
assert!(hashmap.is_empty());
}
#[test]
fn simplex_into_hashmap_multiple() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
vertex!([1.0, 1.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().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::try_into_hashmap(once(simplex1).chain(once(simplex2)).chain(simplices_iter))
.unwrap();
assert!(hashmap.len() >= 2);
assert!(hashmap.contains_key(&uuid1));
assert!(hashmap.contains_key(&uuid2));
}
#[test]
fn simplex_try_into_hashmap_rejects_duplicate_uuid() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let simplex = simplex_ref.clone();
let uuid = simplex.uuid();
let duplicate =
Simplex::try_new_with_uuid(simplex.vertices().to_vec(), uuid, None).unwrap();
assert_matches!(
Simplex::try_into_hashmap([simplex, duplicate]),
Err(TdsConstructionError::DuplicateUuid {
entity: EntityKind::Simplex,
uuid: duplicate_uuid,
}) if duplicate_uuid == uuid
);
}
#[test]
fn simplex_debug_format() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, (), i32, 3> =
DelaunayTriangulationBuilder::new(&vertices)
.simplex_data_type::<i32>()
.build()
.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]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let serialized = serde_json::to_string(&dt).unwrap();
assert!(serialized.contains("vertices"));
assert!(serialized.contains("simplices"));
let tds: Tds<(), (), 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);
}
}
#[test]
fn simplex_deserialization_error_cases() {
let invalid_json_missing_uuid = r#"{"data": null}"#;
let result: Result<Simplex<(), 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<(), 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<(), 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<(), 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<(), 3>, _> = serde_json::from_str(json_unknown_field);
let err = result.expect_err("standalone simplex records should not deserialize");
assert!(
err.to_string()
.contains("standalone Simplex deserialization")
);
let json_with_slotmap_vertices = r#"{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"vertices": [{"idx": 1, "version": 1}]
}"#;
let result: Result<Simplex<(), 3>, _> = serde_json::from_str(json_with_slotmap_vertices);
let err = result.expect_err("slotmap vertex keys must be rejected");
assert!(
err.to_string().contains("storage-local simplex state"),
"unexpected error for serialized slotmap vertex keys: {err}"
);
let json_with_neighbors = r#"{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"neighbors": [null, null, null, null]
}"#;
let result: Result<Simplex<(), 3>, _> = serde_json::from_str(json_with_neighbors);
let err = result.expect_err("slotmap neighbor keys must be rejected");
assert!(
err.to_string().contains("storage-local simplex state"),
"unexpected error for serialized slotmap neighbor keys: {err}"
);
}
#[test]
fn simplex_serialization_data_field_handling() {
let minimal_valid_json = r#"{"uuid": "550e8400-e29b-41d4-a716-446655440000"}"#;
let result: Result<Simplex<(), 3>, _> = serde_json::from_str(minimal_valid_json);
let err = result.expect_err("minimal standalone simplex record should fail");
assert!(
err.to_string()
.contains("standalone Simplex deserialization")
);
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, (), i32, 3> =
DelaunayTriangulationBuilder::new(&vertices)
.simplex_data_type::<i32>()
.build()
.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 err = serde_json::from_str::<Simplex<i32, 3>>(&serialized)
.expect_err("standalone Simplex deserialization should require TDS UUID mapping");
assert!(
err.to_string()
.contains("standalone Simplex deserialization")
);
let dt = DelaunayTriangulation::builder(&vertices).build().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 err = serde_json::from_str::<Simplex<Option<i32>, 3>>(&serialized_none)
.expect_err("standalone Simplex deserialization should require TDS UUID mapping");
assert!(
err.to_string()
.contains("standalone Simplex deserialization")
);
let json_with_null = r#"{"uuid":"550e8400-e29b-41d4-a716-446655440000","data":null}"#;
let err = serde_json::from_str::<Simplex<Option<i32>, 3>>(json_with_null)
.expect_err("standalone simplex record should fail");
assert!(
err.to_string()
.contains("standalone Simplex deserialization")
);
}
#[test]
fn simplex_coordinate_ranges() {
let negative_vertices = vec![
vertex!([-1.0, -1.0, -1.0]).unwrap(),
vertex!([-2.0, -1.0, -1.0]).unwrap(),
vertex!([-1.0, -2.0, -1.0]).unwrap(),
vertex!([-1.0, -1.0, -2.0]).unwrap(),
];
let dt_neg = DelaunayTriangulation::builder(&negative_vertices)
.build()
.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]).unwrap(),
vertex!([2e6, 1e6, 1e6]).unwrap(),
vertex!([1e6, 2e6, 1e6]).unwrap(),
vertex!([1e6, 1e6, 2e6]).unwrap(),
];
let dt_large = DelaunayTriangulation::builder(&large_vertices)
.build()
.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]).unwrap(),
vertex!([1e-3, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1e-3, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1e-3]).unwrap(),
];
let dt_small = DelaunayTriangulation::builder(&small_vertices)
.build()
.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]).unwrap();
let vertex2 = vertex!([1.0, 0.0]).unwrap();
let vertex3 = vertex!([0.0, 1.0]).unwrap();
let vertices = vec![vertex1, vertex2, vertex3];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_points: Vec<Point<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]).unwrap();
let vertex2 = vertex!([1.0, 0.0, 0.0]).unwrap();
let vertex3 = vertex!([0.0, 1.0, 0.0]).unwrap();
let vertex4 = vertex!([0.0, 0.0, 1.0]).unwrap(); let vertex_outside: Vertex<(), 3> = vertex!([2.0, 2.0, 2.0]).unwrap();
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = dt.simplex(simplex_key).unwrap();
let outside_key = dt.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]; data = 1).unwrap();
let vertex2 = vertex!([1.0, 0.0, 0.0]; data = 1).unwrap();
let vertex3 = vertex!([0.0, 1.0, 0.0]; data = 1).unwrap();
let vertex4 = vertex!([0.0, 0.0, 1.0]; data = 2).unwrap();
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, (), 3> =
DelaunayTriangulationBuilder::new(&vertices)
.build()
.unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_far_outside: Vertex<i32, 3> = vertex!([10.0, 10.0, 10.0]; data = 4).unwrap();
let vertex_points: Vec<Point<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<i32, 3> = vertex!([0.0, 0.0, 0.0]; data = 3).unwrap();
let vertex_points: Vec<Point<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]).unwrap();
let vertex2 = vertex!([1.0, 0.0]).unwrap();
let vertex3 = vertex!([0.0, 1.0]).unwrap();
let vertices = vec![vertex1, vertex2, vertex3];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_far_outside: Vertex<(), 2> = vertex!([10.0, 10.0]).unwrap();
let vertex_points: Vec<Point<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<(), 2> = vertex!([0.33, 0.33]).unwrap();
let vertex_points: Vec<Point<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]).unwrap();
let vertex2 = vertex!([1.0, 0.0, 0.0]).unwrap();
let vertex3 = vertex!([0.0, 1.0, 0.0]).unwrap();
let vertex4 = vertex!([0.0, 0.0, 1.0]).unwrap();
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let simplex = &dt.tds().simplex(simplex_key).unwrap();
let vertex_points: Vec<Point<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]).unwrap();
let vertex2 = vertex!([1.0, 0.0, 0.0]).unwrap();
let vertex3 = vertex!([0.0, 1.0, 0.0]).unwrap();
let vertex4 = vertex!([0.0, 0.0, 1.0]).unwrap();
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet_views = dt
.tds()
.try_simplex_facets(simplex_key)
.expect("Failed to get facet iterator")
.collect::<Result<Vec<_>, _>>()
.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();
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();
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]).unwrap();
let vertex2 = vertex!([0.0, 1.0, 0.0]).unwrap();
let vertex3 = vertex!([1.0, 0.0, 0.0]).unwrap();
let vertex4 = vertex!([1.0, 1.0, 1.0]).unwrap();
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.simplices().next().unwrap().0;
let facet_views = dt
.simplex_facets(simplex_key)
.expect("Failed to get facet iterator")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to get facet views");
for facet_view in &facet_views {
let opposite_vertex = facet_view.opposite_vertex();
let opposite_vertex_key = dt.vertex_key_from_uuid(&opposite_vertex.uuid()).unwrap();
let facet_vertices = facet_view.vertices();
let facet_vertex_keys: Vec<_> = facet_vertices
.map(|v| dt.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_high_dimensional() {
let vertex1 = vertex!([0.0, 0.0, 0.0, 0.0, 0.0]).unwrap();
let vertex2 = vertex!([1.0, 0.0, 0.0, 0.0, 0.0]).unwrap();
let vertex3 = vertex!([0.0, 1.0, 0.0, 0.0, 0.0]).unwrap();
let vertex4 = vertex!([0.0, 0.0, 1.0, 0.0, 0.0]).unwrap();
let vertex5 = vertex!([0.0, 0.0, 0.0, 1.0, 0.0]).unwrap();
let vertex6 = vertex!([0.0, 0.0, 0.0, 0.0, 1.0]).unwrap();
let vertices = vec![vertex1, vertex2, vertex3, vertex4, vertex5, vertex6];
let dt = DelaunayTriangulation::builder(&vertices).build().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!(
dt.tds()
.try_simplex_facets(simplex_key)
.expect("Failed to get facets")
.len(),
6
); }
#[test]
fn simplex_vertex_data_consistency() {
let vertex1 = vertex!([0.0, 0.0, 0.0]; data = 1).unwrap();
let vertex2 = vertex!([1.0, 0.0, 0.0]; data = 2).unwrap();
let vertex3 = vertex!([0.0, 1.0, 0.0]; data = 3).unwrap();
let vertex4 = vertex!([0.0, 0.0, 1.0]; data = 4).unwrap();
let vertices = vec![vertex1, vertex2, vertex3, vertex4];
let mut dt: DelaunayTriangulation<AdaptiveKernel<f64>, i32, u32, 3> =
DelaunayTriangulationBuilder::new(&vertices)
.simplex_data_type::<u32>()
.build()
.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 = dt
.tds()
.try_simplex_facets(simplex_key)
.expect("Failed to get facet iterator");
for facet_view in facet_views {
let facet_view = facet_view.expect("Failed to get facet view");
let vertices = facet_view.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]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 0.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices_3d)
.build()
.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]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices_2d)
.build()
.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]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 0.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().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]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
];
let result = DelaunayTriangulation::builder(&insufficient_vertices).build();
assert!(
result.is_err(),
"TDS should fail with insufficient vertices"
);
let vertices_2d = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices_2d)
.build()
.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]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let vkeys: Vec<_> = dt.tds().vertices().map(|(k, _)| k).collect();
let err = Simplex::<(), 3>::try_new_with_data(vec![vkeys[0], vkeys[1], vkeys[2]], None)
.unwrap_err();
assert_matches!(
err,
SimplexValidationError::InsufficientVertices {
actual: 3,
expected: 4,
dimension: 3,
}
);
let err =
Simplex::<(), 3>::try_new_with_data(vec![vkeys[0], vkeys[1], vkeys[2], vkeys[0]], None)
.unwrap_err();
assert_matches!(err, SimplexValidationError::DuplicateVertices);
}
#[test]
fn periodic_simplex_accepts_repeated_key_with_distinct_lifted_offsets() {
let keys = synthetic_vertex_keys(3);
let simplex = Simplex::<(), 3>::try_new_periodic(
vec![keys[0], keys[1], keys[2], keys[0]],
vec![[0, 0, 0], [0, 0, 0], [0, 0, 0], [1, 0, 0]],
)
.expect("distinct lifted identities should form a periodic simplex");
assert!(simplex.is_valid().is_ok());
}
#[test]
fn periodic_simplex_rejects_repeated_lifted_identity() {
let keys = synthetic_vertex_keys(3);
let error = Simplex::<(), 3>::try_new_periodic(
vec![keys[0], keys[1], keys[2], keys[0]],
vec![[1, 0, 0], [0, 0, 0], [0, 0, 0], [1, 0, 0]],
)
.expect_err("an identical key-offset pair must remain invalid");
assert_eq!(error, SimplexValidationError::DuplicateVertices);
}
#[test]
fn periodic_simplex_rejects_misaligned_shapes_and_reports_corruption() {
let keys = synthetic_vertex_keys(4);
assert_eq!(
Simplex::<(), 3>::try_new_periodic(keys[..3].to_vec(), vec![[0; 3]; 3]).unwrap_err(),
SimplexValidationError::InsufficientVertices {
actual: 3,
expected: 4,
dimension: 3,
}
);
assert_eq!(
Simplex::<(), 3>::try_new_periodic(keys.clone(), vec![[0; 3]; 3]).unwrap_err(),
SimplexValidationError::PeriodicOffsetLengthMismatch {
expected: 4,
found: 3,
}
);
let mut misaligned =
Simplex::<(), 3>::try_new_periodic(keys.clone(), vec![[0; 3]; 4]).unwrap();
misaligned.periodic_vertex_offsets.as_mut().unwrap().pop();
let mismatch = SimplexValidationError::PeriodicOffsetLengthMismatch {
expected: 4,
found: 3,
};
assert_eq!(misaligned.is_valid(), Err(mismatch.clone()));
assert_eq!(
misaligned.simplex_report().unwrap_err().violations,
vec![mismatch]
);
let mut duplicate = Simplex::<(), 3>::try_new_periodic(keys, vec![[0; 3]; 4]).unwrap();
duplicate.vertices[3] = duplicate.vertices[0];
assert_eq!(
duplicate.simplex_report().unwrap_err().violations,
vec![SimplexValidationError::DuplicateVertices]
);
}
#[test]
fn simplex_is_valid_rejects_insufficient_and_duplicate_vertices() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().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]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().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_try_ensure_neighbors_buffer_mut_rejects_malformed_existing_buffer() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let (_, simplex_ref) = dt.simplices().next().unwrap();
let mut simplex = simplex_ref.clone();
simplex.ensure_neighbors_buffer_mut().truncate(2);
assert_matches!(
simplex.try_ensure_neighbors_buffer_mut(),
Err(SimplexValidationError::InvalidNeighborsLength {
actual: 2,
expected: 3,
dimension: 2
})
);
assert_eq!(simplex.neighbor_slots().unwrap().len(), 2);
}
#[test]
fn simplex_neighbor_views_distinguish_unassigned_boundary_and_neighbor_slots() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().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]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().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]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().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]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().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]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let mut dt = DelaunayTriangulation::builder(&vertices).build().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 simplex.number_of_vertices() <= usize::from(u8::MAX) + 1 {
simplex.push_vertex_key(vkey0);
}
assert!(simplex.number_of_vertices() > usize::from(u8::MAX) + 1);
}
let err = dt
.tds()
.try_simplex_facets(simplex_key)
.err()
.expect("Expected try_simplex_facets to fail on vertex_count overflow");
assert_matches!(
err,
FacetError::InvalidFacetIndexOverflow {
original_index,
facet_count,
} if original_index == usize::from(u8::MAX) + 1
&& facet_count > usize::from(u8::MAX) + 1
);
}
#[test]
fn simplex_deserialize_rejects_missing_uuid_and_duplicate_fields_and_invalid_uuid() {
let err = serde_json::from_str::<Simplex<i32, 3>>("null").unwrap_err();
assert!(err.to_string().contains("a Simplex struct"));
let err = serde_json::from_str::<Simplex<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<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<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<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,\"extra\":[1,2,3]}}");
let err = serde_json::from_str::<Simplex<i32, 3>>(&json).unwrap_err();
assert!(
err.to_string()
.contains("standalone Simplex deserialization")
);
}
#[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]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt1 = DelaunayTriangulation::builder(&vertices_2d)
.build()
.unwrap();
let (_, simplex_2d) = dt1.tds().simplices().next().unwrap();
let dt2 = DelaunayTriangulation::builder(&vertices_2d)
.build()
.unwrap();
let (_, simplex_2d_copy) = dt2.tds().simplices().next().unwrap();
assert_eq!(
simplex_2d, simplex_2d_copy,
"Identical 2D simplices should be equal"
);
}
#[test]
fn test_try_simplex_facets() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let simplex_key = dt.tds().simplex_keys().next().unwrap();
let facet_iter = dt
.tds()
.try_simplex_facets(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().count();
assert_eq!(vertex_count, 3, "Facet {i} should have 3 vertices");
}
let facet_iter2 = dt
.tds()
.try_simplex_facets(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 = dt
.tds()
.try_simplex_facets(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"
);
}
#[test]
fn test_simplex_data_accessor() {
let vertices = [
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let mut dt = DelaunayTriangulationBuilder::new(&vertices)
.simplex_data_type::<i32>()
.build()
.unwrap();
let key = dt.simplices().next().unwrap().0;
assert_eq!(dt.tds().simplex(key).unwrap().data(), None);
dt.set_simplex_data(key, Some(99)).unwrap();
assert_eq!(dt.tds().simplex(key).unwrap().data(), Some(&99));
}
}