#![allow(clippy::similar_names)]
#![forbid(unsafe_code)]
use super::vertex::Vertex;
use super::{
facet::FacetError,
tds::{CellKey, Tds, VertexKey},
traits::DataType,
util::{UuidValidationError, make_uuid, validate_uuid},
vertex::VertexValidationError,
};
use crate::core::collections::{CellVertexBuffer, FastHashMap, FastHashSet, NeighborBuffer};
use crate::geometry::traits::coordinate::{CoordinateConversionError, CoordinateScalar};
use serde::{
Deserialize, Deserializer, Serialize,
de::{self, IgnoredAny, MapAccess, Visitor},
};
use std::{
cmp,
fmt::{self, Debug},
hash::{Hash, Hasher},
marker::PhantomData,
};
use thiserror::Error;
use uuid::Uuid;
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum CellValidationError {
#[error("Invalid vertex: {source}")]
InvalidVertex {
#[from]
source: VertexValidationError,
},
#[error("Invalid UUID: {source}")]
InvalidUuid {
#[from]
source: UuidValidationError,
},
#[error("Duplicate vertices: cell contains non-unique vertices which is not allowed")]
DuplicateVertices,
#[error(
"Insufficient vertices: cell 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("Vertex key {key:?} not found in TDS (indicates TDS corruption or inconsistency)")]
VertexKeyNotFound {
key: VertexKey,
},
}
impl From<crate::geometry::matrix::StackMatrixDispatchError> for CellValidationError {
fn from(source: crate::geometry::matrix::StackMatrixDispatchError) -> Self {
CoordinateConversionError::from(source).into()
}
}
#[derive(Clone, Debug)]
pub struct Cell<T, U, V, const D: usize> {
vertices: CellVertexBuffer,
uuid: Uuid,
pub(crate) neighbors: Option<NeighborBuffer<Option<CellKey>>>,
pub(crate) data: Option<V>,
pub(crate) periodic_vertex_offsets: Option<Vec<[i8; D]>>,
_phantom: PhantomData<(T, U)>,
}
impl<T, U, V, const D: usize> Serialize for Cell<T, U, V, D>
where
U: DataType,
V: DataType,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let has_data = self.data.is_some();
let field_count = if has_data { 2 } else { 1 };
let mut state = serializer.serialize_struct("Cell", 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 Cell<T, U, V, D>
where
U: DataType,
V: DataType,
{
fn deserialize<De>(deserializer: De) -> Result<Self, De::Error>
where
De: Deserializer<'de>,
{
struct CellVisitor<T, U, V, const D: usize>
where
U: DataType,
V: DataType,
{
_phantom: PhantomData<(T, U, V)>,
}
impl<'de, T, U, V, const D: usize> Visitor<'de> for CellVisitor<T, U, V, D>
where
U: DataType,
V: DataType,
{
type Value = Cell<T, U, V, D>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a Cell struct")
}
fn visit_map<A>(self, mut map: A) -> Result<Cell<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"))?;
crate::core::util::validate_uuid(&uuid)
.map_err(|e| de::Error::custom(format!("invalid uuid: {e}")))?;
let data = data.flatten();
let vertices = CellVertexBuffer::new();
Ok(Cell {
vertices,
uuid,
neighbors: None, data,
periodic_vertex_offsets: None,
_phantom: PhantomData,
})
}
}
const FIELDS: &[&str] = &["uuid", "data"];
deserializer.deserialize_struct(
"Cell",
FIELDS,
CellVisitor {
_phantom: PhantomData,
},
)
}
}
impl<T, U, V, const D: usize> Cell<T, U, V, D>
where
U: DataType,
V: DataType,
{
pub(crate) fn new(
vertices: impl Into<CellVertexBuffer>,
data: Option<V>,
) -> Result<Self, CellValidationError> {
let vertices = vertices.into();
let actual = vertices.len();
if actual != D + 1 {
return Err(CellValidationError::InsufficientVertices {
actual,
expected: D + 1,
dimension: D,
});
}
let mut seen: FastHashSet<VertexKey> = FastHashSet::default();
for &vkey in &vertices {
if !seen.insert(vkey) {
return Err(CellValidationError::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]
pub const fn neighbors(&self) -> Option<&NeighborBuffer<Option<CellKey>>> {
self.neighbors.as_ref()
}
#[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: Vec<[i8; D]>) {
assert_eq!(
offsets.len(),
self.vertices.len(),
"set_periodic_vertex_offsets: offsets.len() ({}) must match self.vertices.len() ({}); refusing to update self.periodic_vertex_offsets",
offsets.len(),
self.vertices.len(),
);
self.periodic_vertex_offsets = Some(offsets);
}
#[inline]
pub(crate) fn mirror_facet_index(
&self,
facet_idx: usize,
neighbor_cell: &Self,
) -> Option<usize> {
if facet_idx >= self.vertices.len() {
return None;
}
debug_assert_eq!(
self.vertices().len(),
neighbor_cell.vertices().len(),
"mirror_facet_index requires cells with matching vertex counts",
);
let mut facet_vertices: CellVertexBuffer = CellVertexBuffer::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_cell.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]
#[cfg_attr(
not(test),
expect(dead_code, reason = "Currently only used by unit tests")
)]
pub(crate) fn ensure_neighbors_buffer_mut(&mut self) -> &mut NeighborBuffer<Option<CellKey>> {
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, None);
buffer
})
}
}
impl<T, U, V, const D: usize> Cell<T, U, V, D>
where
U: DataType,
V: DataType,
{
#[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<(), CellValidationError> {
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<crate::core::collections::CellVertexUuidBuffer, CellValidationError> {
self.vertices
.iter()
.map(|&vkey| {
tds.get_vertex_by_key(vkey)
.map(Vertex::uuid)
.ok_or(CellValidationError::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, CellValidationError>> + 'a {
self.vertices.iter().map(move |&vkey| {
tds.get_vertex_by_key(vkey)
.map(Vertex::uuid)
.ok_or(CellValidationError::VertexKeyNotFound { key: vkey })
})
}
#[inline]
pub const fn dim(&self) -> usize {
D
}
#[must_use]
pub fn into_hashmap<I>(cells: I) -> FastHashMap<Uuid, Self>
where
I: IntoIterator<Item = Self>,
{
cells.into_iter().map(|c| (c.uuid, c)).collect()
}
pub fn is_valid(&self) -> Result<(), CellValidationError> {
validate_uuid(&self.uuid)?;
if self.vertices.len() != D + 1 {
return Err(CellValidationError::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(CellValidationError::DuplicateVertices);
}
}
if let Some(ref neighbors) = self.neighbors
&& neighbors.len() != D + 1
{
return Err(CellValidationError::InvalidNeighborsLength {
actual: neighbors.len(),
expected: D + 1,
dimension: D,
});
}
Ok(())
}
}
impl<T, U, V, const D: usize> Cell<T, U, V, D>
where
T: CoordinateScalar + PartialEq + PartialOrd,
U: DataType,
V: DataType,
{
pub fn facet_views_from_tds(
tds: &Tds<T, U, V, D>,
cell_key: CellKey,
) -> Result<Vec<crate::core::facet::FacetView<'_, T, U, V, D>>, FacetError> {
let cell = tds
.get_cell(cell_key)
.ok_or(FacetError::CellNotFoundInTriangulation)?;
let vertex_count = cell.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 = crate::core::util::usize_to_u8(idx, vertex_count)?;
facet_views.push(crate::core::facet::FacetView::new(
tds,
cell_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 {
let self_vertices: Option<Vec<_>> = self
.vertices()
.iter()
.map(|&vkey| self_tds.get_vertex_by_key(vkey))
.collect();
let other_vertices: Option<Vec<_>> = other
.vertices()
.iter()
.map(|&vkey| other_tds.get_vertex_by_key(vkey))
.collect();
let (Some(mut self_vertices), Some(mut other_vertices)) = (self_vertices, other_vertices)
else {
return false;
};
self_vertices.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
other_vertices.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
self_vertices == other_vertices
}
pub fn facet_view_iter(
tds: &Tds<T, U, V, D>,
cell_key: CellKey,
) -> Result<
impl ExactSizeIterator<Item = Result<crate::core::facet::FacetView<'_, T, U, V, D>, FacetError>>,
FacetError,
> {
let cell = tds
.get_cell(cell_key)
.ok_or(FacetError::CellNotFoundInTriangulation)?;
let vertex_count = cell.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 = crate::core::util::usize_to_u8(idx, vertex_count)?;
crate::core::facet::FacetView::new(tds, cell_key, facet_index)
}))
}
}
impl<T, U, V, const D: usize> PartialEq for Cell<T, U, V, D>
where
U: DataType,
V: DataType,
{
#[inline]
fn eq(&self, other: &Self) -> bool {
let mut self_keys: CellVertexBuffer = self.vertices.iter().copied().collect();
let mut other_keys: CellVertexBuffer = 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 Cell<T, U, V, D>
where
U: DataType,
V: DataType,
{
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
let mut self_keys: CellVertexBuffer = self.vertices.iter().copied().collect();
let mut other_keys: CellVertexBuffer = 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 Cell<T, U, V, D>
where
U: DataType,
V: DataType,
{
}
impl<T, U, V, const D: usize> Hash for Cell<T, U, V, D>
where
U: DataType,
V: DataType,
{
fn hash<H: Hasher>(&self, state: &mut H) {
let mut sorted_keys: CellVertexBuffer = 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::core::vertex::vertex;
use crate::geometry::point::Point;
use crate::geometry::predicates::insphere;
use crate::geometry::util::{circumcenter, circumradius, circumradius_with_center};
use approx::assert_relative_eq;
use std::{cmp, collections::hash_map::DefaultHasher, hash::Hasher};
type TestVertex3D = Vertex<f64, (), 3>;
type TestVertex2D = Vertex<f64, (), 2>;
use crate::geometry::kernel::AdaptiveKernel;
use crate::prelude::DelaunayTriangulation;
use crate::triangulation::builder::DelaunayTriangulationBuilder;
macro_rules! test_cell_dimensions {
($(
$test_name:ident => $dim:expr => $vertices:expr
),+ $(,)?) => {
$(
#[test]
fn $test_name() {
let vertices = $vertices;
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, cell) = dt.cells().next().unwrap();
assert_cell_properties(cell, $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 (_, cell_ref) = dt.cells().next().unwrap();
let mut cell = cell_ref.clone();
cell.data = Some(42);
assert_cell_properties(&cell, $dim + 1, $dim);
assert_eq!(cell.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 (_, cell) = dt.cells().next().unwrap();
let mut cell = cell.clone();
cell.data = Some(99);
let serialized = serde_json::to_string(&cell).unwrap();
assert!(serialized.contains("\"data\":"));
let deserialized: Cell<f64, (), i32, $dim> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.data, Some(99));
assert_eq!(deserialized.uuid(), cell.uuid());
let vertices = $vertices;
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, cell) = dt.cells().next().unwrap();
let serialized = serde_json::to_string(&cell).unwrap();
assert!(!serialized.contains("\"data\":"));
let deserialized: Cell<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 (_, cell1) = dt1.cells().next().unwrap();
let dt2 = DelaunayTriangulation::new(&vertices2).unwrap();
let (_, cell2) = dt2.cells().next().unwrap();
assert_ne!(cell1.uuid(), cell2.uuid());
assert!(!cell1.uuid().is_nil());
assert!(!cell2.uuid().is_nil());
}
}
)+
};
}
test_cell_dimensions! {
cell_2d => 2 => vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
],
cell_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]),
],
cell_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]),
],
cell_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_cell_properties<T, U, V, const D: usize>(
cell: &Cell<T, U, V, D>,
expected_vertices: usize,
expected_dim: usize,
) where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
assert_eq!(cell.number_of_vertices(), expected_vertices);
assert_eq!(cell.dim(), expected_dim);
assert!(!cell.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 cell_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 (_, cell) = dt.cells().next().unwrap();
assert_eq!(cell.number_of_vertices(), 4);
assert_eq!(cell.dim(), 3);
assert!(cell.data.is_none());
assert!(!cell.uuid().is_nil());
let cell_coords: Vec<Vec<f64>> = cell
.vertices()
.iter()
.map(|&vkey| {
dt.tds()
.get_vertex_by_key(vkey)
.unwrap()
.point()
.coords()
.as_slice()
.to_vec()
})
.collect();
for original in &vertices {
let original_coords = original.point().coords().as_slice();
assert!(
cell_coords.iter().any(|coords| {
coords
.iter()
.zip(original_coords)
.all(|(a, b)| (a - b).abs() < f64::EPSILON)
}),
"Input vertex {original_coords:?} not found in cell"
);
}
println!("Cell without data: {cell:?}");
}
#[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 cell1 = dt1.cells().next().unwrap().1;
let cell2 = dt2.cells().next().unwrap().1;
assert!(cell1.eq_by_vertices(dt1.tds(), cell2, dt2.tds()));
println!(" ✓ Cells 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 cell1 = dt1.cells().next().unwrap().1;
let cell2 = dt2.cells().next().unwrap().1;
assert!(cell1.eq_by_vertices(dt1.tds(), cell2, dt2.tds()));
println!(" ✓ 2D cells with same coordinates are equal");
}
#[test]
fn cell_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 (_, cell_ref) = dt.cells().next().unwrap();
let mut cell = cell_ref.clone();
cell.data = Some(42);
assert_eq!(cell.number_of_vertices(), 4);
assert_eq!(cell.dim(), 3);
assert_eq!(cell.data.unwrap(), 42);
assert!(!cell.uuid().is_nil());
let cell_coords: Vec<Vec<f64>> = cell
.vertices()
.iter()
.map(|&vkey| {
dt.tds()
.get_vertex_by_key(vkey)
.unwrap()
.point()
.coords()
.as_slice()
.to_vec()
})
.collect();
for original in &vertices {
let original_coords = original.point().coords().as_slice();
assert!(
cell_coords.iter().any(|coords| {
coords
.iter()
.zip(original_coords)
.all(|(a, b)| (a - b).abs() < f64::EPSILON)
}),
"Input vertex {original_coords:?} not found in cell"
);
}
println!("Cell with data: {cell:?}");
}
#[test]
fn cell_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 (_, cell) = dt.cells().next().unwrap();
assert_eq!(cell.number_of_vertices(), 4);
assert_eq!(cell.dim(), 3);
let cell_data: Vec<i32> = cell
.vertices()
.iter()
.map(|&vkey| dt.tds().get_vertex_by_key(vkey).unwrap().data.unwrap())
.collect();
for expected in &[1, 2, 3, 4] {
assert!(
cell_data.contains(expected),
"Expected vertex data {expected} not found in cell"
);
}
}
#[test]
fn cell_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 (_, cell1) = dt.cells().next().unwrap();
let cell2 = cell1.clone();
assert_eq!(*cell1, cell2);
assert_eq!(cell1.uuid(), cell2.uuid()); assert_eq!(cell1.vertices(), cell2.vertices());
let cell3 = cell1.clone();
assert_eq!(*cell1, cell3);
}
#[test]
fn cell_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 cells: Vec<_> = dt.cells().map(|(_, cell)| cell).collect();
if cells.len() >= 2 {
let cell1 = cells[0];
let cell2 = cells[1];
let has_ordering = cell1 != cell2 || cell1 == cell2;
assert!(has_ordering, "Cells should have some ordering relationship");
}
}
#[test]
fn cell_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 (_, cell1) = dt.cells().next().unwrap();
let cell2 = cell1.clone();
let mut hasher1 = DefaultHasher::new();
let mut hasher2 = DefaultHasher::new();
cell1.hash(&mut hasher1);
cell2.hash(&mut hasher2);
assert_eq!(*cell1, cell2); assert_eq!(hasher1.finish(), hasher2.finish()); assert_eq!(cell1.uuid(), cell2.uuid());
}
#[test]
fn cell_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 (_, cell_ref) = dt.cells().next().unwrap();
let mut cell1 = cell_ref.clone();
cell1.data = Some(42);
let cell2 = cell1.clone();
assert_eq!(cell1, cell2);
}
#[test]
fn cell_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 (_, cell1) = dt.cells().next().unwrap();
let cell2 = cell1.clone();
assert_ne!(cell1.partial_cmp(&cell2), Some(cmp::Ordering::Less));
assert_ne!(cell2.partial_cmp(cell1), Some(cmp::Ordering::Less));
assert!(*cell1 <= cell2);
assert!(cell2 <= *cell1);
assert!(*cell1 >= cell2);
assert!(cell2 >= *cell1);
}
#[test]
fn cell_number_of_vertices() {
let vertices_2d = create_test_vertices_2d();
let dt_2d = DelaunayTriangulation::new(&vertices_2d).unwrap();
let cell_key_2d = dt_2d.tds().cell_keys().next().unwrap();
let triangle = dt_2d.tds().get_cell(cell_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 cell_key_3d = dt_3d.tds().cell_keys().next().unwrap();
let tetrahedron = dt_3d.tds().get_cell(cell_key_3d).unwrap();
assert_eq!(tetrahedron.number_of_vertices(), 4);
}
#[test]
fn cell_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 cells: Vec<_> = dt.cells().map(|(_, cell)| cell).collect();
assert!(
cells.len() >= 2,
"Expected at least 2 cells, got {}",
cells.len()
);
let mut found = None;
for i in 0..cells.len() {
for j in (i + 1)..cells.len() {
let cell_a = cells[i];
let cell_b = cells[j];
let shared: FastHashSet<VertexKey> = cell_a
.vertices()
.iter()
.copied()
.filter(|v| cell_b.vertices().contains(v))
.collect();
if shared.len() == 2 {
let facet_idx_a = cell_a
.vertices()
.iter()
.position(|v| !shared.contains(v))
.expect("cell_a should have one vertex not in the shared facet");
let facet_idx_b = cell_b
.vertices()
.iter()
.position(|v| !shared.contains(v))
.expect("cell_b should have one vertex not in the shared facet");
found = Some((cell_a, cell_b, facet_idx_a, facet_idx_b));
break;
}
}
if found.is_some() {
break;
}
}
let Some((cell_a, cell_b, facet_idx_a, facet_idx_b)) = found else {
panic!("Expected to find a pair of neighboring cells that share an edge");
};
assert_eq!(
cell_a.mirror_facet_index(facet_idx_a, cell_b),
Some(facet_idx_b)
);
assert_eq!(
cell_b.mirror_facet_index(facet_idx_b, cell_a),
Some(facet_idx_a)
);
assert_eq!(
cell_a.mirror_facet_index(cell_a.number_of_vertices(), cell_b),
None
);
}
#[test]
fn cell_mirror_facet_index_returns_none_when_cells_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 cells: Vec<_> = dt.cells().map(|(_, cell)| cell).collect();
assert!(
cells.len() >= 3,
"Expected at least 3 cells, got {}",
cells.len()
);
let mut non_adjacent = None;
'outer: for i in 0..cells.len() {
for j in (i + 1)..cells.len() {
let cell_a = cells[i];
let cell_b = cells[j];
let shared_count = cell_a
.vertices()
.iter()
.filter(|v| cell_b.vertices().contains(v))
.count();
if shared_count < 2 {
non_adjacent = Some((cell_a, cell_b));
break 'outer;
}
}
}
let Some((cell_a, cell_b)) = non_adjacent else {
panic!("Expected to find a pair of non-adjacent cells");
};
assert_eq!(cell_a.mirror_facet_index(0, cell_b), None);
}
#[test]
fn cell_dim() {
let vertices_2d = create_test_vertices_2d();
let dt_2d = DelaunayTriangulation::new(&vertices_2d).unwrap();
let cell_key_2d = dt_2d.tds().cell_keys().next().unwrap();
let triangle = dt_2d.tds().get_cell(cell_key_2d).unwrap();
assert_eq!(triangle.dim(), 2);
let vertices_3d = create_test_vertices_3d();
let dt_3d = DelaunayTriangulation::new(&vertices_3d).unwrap();
let cell_key_3d = dt_3d.tds().cell_keys().next().unwrap();
let tetrahedron = dt_3d.tds().get_cell(cell_key_3d).unwrap();
assert_eq!(tetrahedron.dim(), 3);
}
#[test]
fn cell_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 cell_key = dt.cells().next().unwrap().0;
let cell = &dt.tds().get_cell(cell_key).unwrap();
let vertex_keys: Vec<_> = dt.tds().vertices().map(|(k, _)| k).collect();
assert!(cell.contains_vertex(vertex_keys[0]));
assert!(cell.contains_vertex(vertex_keys[1]));
assert!(cell.contains_vertex(vertex_keys[2]));
assert!(cell.contains_vertex(vertex_keys[3]));
println!("Cell: {cell:?}");
}
#[test]
fn cell_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 (_, cell_ref) = tds1.cells().next().unwrap();
let mut cell = cell_ref.clone();
cell.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 (_, cell2_ref) = tds2.cells().next().unwrap();
let mut cell2 = cell2_ref.clone();
cell2.data = Some(43);
assert!(cell.has_vertex_in_common(&cell2));
println!("Cell: {cell:?}");
}
#[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 cell_key = dt.cells().next().unwrap().0;
let cell = &dt.tds().get_cell(cell_key).unwrap();
let vertex_uuids = cell.vertex_uuids(dt.tds()).unwrap();
assert_eq!(cell.vertex_uuid_iter(dt.tds()).count(), 4);
for (expected_uuid, returned_uuid) in
cell.vertex_uuid_iter(dt.tds()).zip(vertex_uuids.iter())
{
assert_eq!(expected_uuid.unwrap(), *returned_uuid);
}
let unique_uuids: std::collections::HashSet<_> = vertex_uuids.iter().collect();
assert_eq!(unique_uuids.len(), vertex_uuids.len());
for uuid in cell.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_cell_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_cell() {
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 cell_key = dt.cells().next().unwrap().0;
let cell = &dt.tds().get_cell(cell_key).unwrap();
let vertex_uuids = cell.vertex_uuids(dt.tds()).unwrap();
assert_eq!(cell.vertex_uuid_iter(dt.tds()).count(), 3);
for (expected_uuid, returned_uuid) in
cell.vertex_uuid_iter(dt.tds()).zip(vertex_uuids.iter())
{
assert_eq!(expected_uuid.unwrap(), *returned_uuid);
}
let unique_uuids: std::collections::HashSet<_> = vertex_uuids.iter().collect();
assert_eq!(unique_uuids.len(), vertex_uuids.len());
for uuid in cell.vertex_uuid_iter(dt.tds()) {
assert_ne!(uuid.unwrap(), Uuid::nil());
}
println!("✓ vertex_uuids works correctly for 2D cells");
}
#[test]
fn test_vertex_uuids_4d_cell() {
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 cell_key = dt.cells().next().unwrap().0;
let cell = &dt.tds().get_cell(cell_key).unwrap();
let vertex_uuids = cell.vertex_uuids(dt.tds()).unwrap();
assert_eq!(cell.vertex_uuid_iter(dt.tds()).count(), 5);
for (expected_uuid, returned_uuid) in
cell.vertex_uuid_iter(dt.tds()).zip(vertex_uuids.iter())
{
assert_eq!(expected_uuid.unwrap(), *returned_uuid);
}
let unique_uuids: std::collections::HashSet<_> = vertex_uuids.iter().collect();
assert_eq!(unique_uuids.len(), vertex_uuids.len());
let vertex_data: Vec<i32> = cell
.vertices()
.iter()
.map(|&vkey| dt.tds().get_vertex_by_key(vkey).unwrap().data.unwrap())
.collect();
for expected in 1..=5 {
assert!(
vertex_data.contains(&expected),
"Expected vertex data {expected} not found"
);
}
for uuid in cell.vertex_uuid_iter(dt.tds()) {
assert_ne!(uuid.unwrap(), Uuid::nil());
}
println!("✓ vertex_uuids works correctly for 4D cells");
}
#[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 dt: DelaunayTriangulation<AdaptiveKernel<f32>, (), (), 3> =
DelaunayTriangulation::with_kernel(&AdaptiveKernel::new(), &vertices).unwrap();
let cell_key = dt.cells().next().unwrap().0;
let cell = &dt.tds().get_cell(cell_key).unwrap();
let vertex_uuids = cell.vertex_uuids(dt.tds()).unwrap();
assert_eq!(cell.vertex_uuid_iter(dt.tds()).count(), 4);
let first_vertex_key = cell.vertices()[0];
let first_vertex = &dt.tds().get_vertex_by_key(first_vertex_key).unwrap();
assert_relative_eq!(
first_vertex.point().coords()[0],
0.0f32,
epsilon = f32::EPSILON
);
for (expected_uuid, returned_uuid) in
cell.vertex_uuid_iter(dt.tds()).zip(vertex_uuids.iter())
{
assert_eq!(expected_uuid.unwrap(), *returned_uuid);
}
let unique_uuids: std::collections::HashSet<_> = vertex_uuids.iter().collect();
assert_eq!(unique_uuids.len(), vertex_uuids.len());
for uuid in cell.vertex_uuid_iter(dt.tds()) {
assert_ne!(uuid.unwrap(), Uuid::nil());
}
println!("✓ vertex_uuids works correctly with f32 coordinates");
}
#[test]
fn cell_1d() {
let vertices = vec![vertex!([0.0]), vertex!([1.0])];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, cell) = dt.cells().next().unwrap();
assert_cell_properties(cell, 2, 1);
}
#[test]
fn cell_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 (_, cell) = dt.cells().next().unwrap();
assert_eq!(cell.number_of_vertices(), 3);
assert_eq!(cell.dim(), 2);
assert!(!cell.uuid().is_nil());
}
#[test]
fn cell_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 cell_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 (_, cell) = dt.cells().next().unwrap();
assert!(cell.neighbors.is_some() || cell.neighbors.is_none());
}
#[test]
fn cell_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 (_, cell) = dt.cells().next().unwrap();
assert!(cell.data.is_none());
}
#[test]
fn cell_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 (_, cell_ref) = dt.cells().next().unwrap();
let mut cell = cell_ref.clone();
cell.data = Some(42);
assert_eq!(cell.data.unwrap(), 42);
}
#[test]
fn cell_into_hashmap_empty() {
let hashmap = Cell::<f64, (), (), 3>::into_hashmap([]);
assert!(hashmap.is_empty());
}
#[test]
fn cell_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 cells_iter = dt.cells().map(|(_, cell)| cell.clone());
let cell1 = cells_iter
.next()
.expect("Need at least 2 cells for this test");
let cell2 = cells_iter
.next()
.expect("Need at least 2 cells for this test");
let uuid1 = cell1.uuid();
let uuid2 = cell2.uuid();
let hashmap = Cell::into_hashmap(
std::iter::once(cell1)
.chain(std::iter::once(cell2))
.chain(cells_iter),
);
assert!(hashmap.len() >= 2);
assert!(hashmap.contains_key(&uuid1));
assert!(hashmap.contains_key(&uuid2));
}
#[test]
fn cell_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 (_, cell_ref) = dt.cells().next().unwrap();
let mut cell = cell_ref.clone();
cell.data = Some(42);
let debug_str = format!("{cell:?}");
assert!(debug_str.contains("Cell"));
assert!(!cell.vertices().is_empty());
assert!(!cell.uuid().is_nil());
assert_eq!(cell.data.unwrap(), 42);
}
#[test]
fn cell_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("cells"));
let tds: crate::core::tds::Tds<f64, (), (), 3> = serde_json::from_str(&serialized).unwrap();
let deserialized = DelaunayTriangulation::from_tds(tds, AdaptiveKernel::new());
assert_eq!(deserialized.number_of_vertices(), dt.number_of_vertices());
assert_eq!(deserialized.number_of_cells(), dt.number_of_cells());
assert_eq!(deserialized.dim(), dt.dim());
assert_ne!(deserialized.number_of_cells(), 0);
for (_cell_key, cell) in deserialized.tds().cells() {
assert_eq!(cell.dim(), 3);
assert_eq!(cell.number_of_vertices(), 4);
}
println!("TDS serialization/deserialization test passed");
}
#[test]
fn cell_deserialization_error_cases() {
let invalid_json_missing_uuid = r#"{"data": null}"#;
let result: Result<Cell<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<Cell<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<Cell<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<Cell<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<Cell<f64, (), (), 3>, _> = serde_json::from_str(json_unknown_field);
assert!(result.is_ok(), "Unknown fields should be ignored");
}
#[test]
fn cell_serialization_data_field_handling() {
let minimal_valid_json = r#"{"uuid": "550e8400-e29b-41d4-a716-446655440000"}"#;
let result: Result<Cell<f64, (), (), 3>, _> = serde_json::from_str(minimal_valid_json);
assert!(
result.is_ok(),
"Minimal valid JSON with just UUID should succeed"
);
let cell = result.unwrap();
assert_eq!(
cell.uuid().to_string(),
"550e8400-e29b-41d4-a716-446655440000"
);
assert!(cell.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 (_, cell_ref) = dt.cells().next().unwrap();
let mut cell_with_data = cell_ref.clone();
cell_with_data.data = Some(42);
let serialized = serde_json::to_string(&cell_with_data).unwrap();
assert!(
serialized.contains("\"data\":"),
"Some(data) should include data field"
);
assert!(serialized.contains("42"));
let deserialized: Cell<f64, (), i32, 3> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.data, Some(42));
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let (_, cell_none) = dt.cells().next().unwrap();
let serialized_none = serde_json::to_string(&cell_none).unwrap();
assert!(
!serialized_none.contains("\"data\":"),
"None should omit data field"
);
let deserialized_none: Cell<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 cell_explicit_null: Cell<f64, (), Option<i32>, 3> =
serde_json::from_str(json_with_null).unwrap();
assert_eq!(cell_explicit_null.data, None);
}
#[test]
fn cell_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 (_, cell_neg) = dt_neg.tds().cells().next().unwrap();
assert_eq!(cell_neg.number_of_vertices(), 4);
assert_eq!(cell_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 (_, cell_large) = dt_large.tds().cells().next().unwrap();
assert_eq!(cell_large.number_of_vertices(), 4);
assert_eq!(cell_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 (_, cell_small) = dt_small.tds().cells().next().unwrap();
assert_eq!(cell_small.number_of_vertices(), 4);
assert_eq!(cell_small.dim(), 3);
}
#[test]
fn cell_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 cell_key = dt.cells().next().unwrap().0;
let cell = &dt.tds().get_cell(cell_key).unwrap();
let vertex_points: Vec<Point<f64, 2>> = cell
.vertices()
.iter()
.map(|vk| *dt.tds().get_vertex_by_key(*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 cell_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 cell_key = dt.cells().next().unwrap().0;
let cell = &dt.tds().get_cell(cell_key).unwrap();
let outside_key = dt.tds().vertex_key_from_uuid(&vertex_outside.uuid());
assert!(outside_key.is_none() || !cell.contains_vertex(outside_key.unwrap()));
}
#[test]
fn cell_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 cell_key = dt.cells().next().unwrap().0;
let cell = &dt.tds().get_cell(cell_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>> = cell
.vertices()
.iter()
.map(|vk| *dt.tds().get_vertex_by_key(*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>> = cell
.vertices()
.iter()
.map(|vk| *dt.tds().get_vertex_by_key(*vk).unwrap().point())
.collect();
let result_origin = insphere(&vertex_points, *origin.point());
assert!(result_origin.is_ok());
}
#[test]
fn cell_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 cell_key = dt.cells().next().unwrap().0;
let cell = &dt.tds().get_cell(cell_key).unwrap();
let vertex_far_outside: Vertex<f64, (), 2> = vertex!([10.0, 10.0]);
let vertex_points: Vec<Point<f64, 2>> = cell
.vertices()
.iter()
.map(|vk| *dt.tds().get_vertex_by_key(*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>> = cell
.vertices()
.iter()
.map(|vk| *dt.tds().get_vertex_by_key(*vk).unwrap().point())
.collect();
let result_center = insphere(&vertex_points, *center.point());
assert!(result_center.is_ok());
}
#[test]
fn cell_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 cell_key = dt.cells().next().unwrap().0;
let cell = &dt.tds().get_cell(cell_key).unwrap();
let vertex_points: Vec<Point<f64, 3>> = cell
.vertices()
.iter()
.map(|vk| *dt.tds().get_vertex_by_key(*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 cell_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 cell_key = dt.cells().next().unwrap().0;
let facet_views =
Cell::facet_views_from_tds(dt.tds(), cell_key).expect("Failed to get facet views");
assert_eq!(facet_views.len(), 4, "3D cell 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 cell = dt.tds().get_cell(cell_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!(
cell.vertices().contains(&opposite_key),
"Facet {i} opposite vertex key should be in cell"
);
}
}
#[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 cell_key = dt.cells().next().unwrap().0;
let facet_views =
Cell::facet_views_from_tds(dt.tds(), cell_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::<std::collections::HashSet<_>>()
.len();
assert_eq!(
unique_count,
facet_vertex_keys.len(),
"All facet vertices should be unique"
);
}
}
#[test]
fn cell_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 (_, cell_f32) = dt_f32.cells().next().unwrap();
assert_eq!(cell_f32.number_of_vertices(), 3);
assert_eq!(cell_f32.dim(), 2);
}
#[test]
fn cell_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 cell_key = dt.cells().next().unwrap().0;
let cell = &dt.tds().get_cell(cell_key).unwrap();
assert_eq!(cell.number_of_vertices(), 6);
assert_eq!(cell.dim(), 5);
assert_eq!(
Cell::facet_views_from_tds(dt.tds(), cell_key)
.expect("Failed to get facets")
.len(),
6
); }
#[test]
fn cell_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 cell_key = dt.cells().next().unwrap().0;
if let Some(cell) = dt.tri.tds.cells_mut().get_mut(cell_key) {
cell.data = Some(42u32);
}
let cell = &dt.tds().get_cell(cell_key).unwrap();
let vertex_data: Vec<i32> = cell
.vertices()
.iter()
.map(|&vkey| dt.tds().get_vertex_by_key(vkey).unwrap().data.unwrap())
.collect();
for expected in 1..=4 {
assert!(
vertex_data.contains(&expected),
"Expected vertex data {expected} not found"
);
}
assert_eq!(cell.data.unwrap(), 42u32);
let facet_views =
Cell::facet_views_from_tds(dt.tds(), cell_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 cell_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 (_, cell_3d) = dt.cells().next().unwrap();
assert!(
cell_3d.is_valid().is_ok(),
"Valid 3D cell 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 (_, cell_ref) = dt.cells().next().unwrap();
let mut cell_2d = cell_ref.clone();
assert!(
cell_2d.is_valid().is_ok(),
"Valid 2D cell should pass validation"
);
cell_2d.neighbors = None;
assert!(
cell_2d.is_valid().is_ok(),
"Cell with no neighbors should be valid"
);
cell_2d.neighbors = Some(vec![None, None, None].into());
assert!(
cell_2d.is_valid().is_ok(),
"Cell with correct neighbors length should be valid"
);
}
#[test]
fn cell_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 (_, cell_ref) = dt.cells().next().unwrap();
let mut invalid_uuid_cell = cell_ref.clone();
invalid_uuid_cell.uuid = uuid::Uuid::nil();
assert!(
matches!(
invalid_uuid_cell.is_valid(),
Err(CellValidationError::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 (_, cell_ref) = dt.cells().next().unwrap();
let mut cell_wrong_neighbors = cell_ref.clone();
cell_wrong_neighbors.neighbors = Some(vec![None, None].into());
assert!(
matches!(
cell_wrong_neighbors.is_valid(),
Err(CellValidationError::InvalidNeighborsLength {
actual: 2,
expected: 3,
dimension: 2
})
),
"Wrong neighbors count should fail validation"
);
cell_wrong_neighbors.neighbors = Some(vec![None, None, None, None].into());
assert!(
matches!(
cell_wrong_neighbors.is_valid(),
Err(CellValidationError::InvalidNeighborsLength {
actual: 4,
expected: 3,
dimension: 2
})
),
"Wrong neighbors count should fail validation"
);
}
#[test]
fn cell_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 =
Cell::<f64, (), (), 3>::new(vec![vkeys[0], vkeys[1], vkeys[2]], None).unwrap_err();
assert!(matches!(
err,
CellValidationError::InsufficientVertices {
actual: 3,
expected: 4,
dimension: 3,
}
));
let err = Cell::<f64, (), (), 3>::new(vec![vkeys[0], vkeys[1], vkeys[2], vkeys[0]], None)
.unwrap_err();
assert!(matches!(err, CellValidationError::DuplicateVertices));
}
#[test]
fn cell_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 (_, cell_ref) = dt.cells().next().unwrap();
let mut wrong_len = cell_ref.clone();
wrong_len.vertices.pop();
assert!(matches!(
wrong_len.is_valid(),
Err(CellValidationError::InsufficientVertices { .. })
));
let mut dup = cell_ref.clone();
dup.vertices[1] = dup.vertices[0];
assert!(matches!(
dup.is_valid(),
Err(CellValidationError::DuplicateVertices)
));
}
#[test]
fn cell_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 (cell_key, cell_ref) = dt.cells().next().unwrap();
let mut cell = cell_ref.clone();
assert!(cell.neighbors.is_none());
let buf = cell.ensure_neighbors_buffer_mut();
assert_eq!(buf.len(), 3);
assert!(buf.iter().all(Option::is_none));
buf[0] = Some(cell_key);
let buf2 = cell.ensure_neighbors_buffer_mut();
assert_eq!(buf2[0], Some(cell_key));
}
#[test]
fn cell_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 (cell_key, cell_ref) = dt.cells().next().unwrap();
let mut cell = cell_ref.clone();
cell.neighbors = Some(vec![Some(cell_key), None, Some(cell_key)].into());
cell.set_periodic_vertex_offsets(vec![[1, 0], [2, 0], [3, 0]]);
let before_vertices = cell.vertices().to_vec();
let before_neighbors = cell.neighbors().unwrap().to_vec();
let before_offsets = cell.periodic_vertex_offsets().unwrap().to_vec();
cell.swap_vertex_slots(0, 2);
assert_eq!(cell.vertices()[0], before_vertices[2]);
assert_eq!(cell.vertices()[2], before_vertices[0]);
let neighbors = cell.neighbors().unwrap();
assert_eq!(neighbors[0], before_neighbors[2]);
assert_eq!(neighbors[2], before_neighbors[0]);
let offsets = cell.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 cell_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 (_, cell_ref) = dt.cells().next().unwrap();
let mut cell = cell_ref.clone();
cell.neighbors = Some(vec![None, None].into());
cell.swap_vertex_slots(0, 2);
}
#[test]
fn cell_facet_view_helpers_reject_excessive_vertex_count() {
use crate::core::facet::FacetError;
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 cell_key = dt.tds().cell_keys().next().unwrap();
let vkey0 = {
let cell = dt.tds().get_cell(cell_key).unwrap();
cell.vertices()[0]
};
{
let cell = dt
.tri
.tds
.cells_mut()
.get_mut(cell_key)
.expect("cell key should be valid in test");
while u8::try_from(cell.number_of_vertices()).is_ok() {
cell.push_vertex_key(vkey0);
}
assert!(u8::try_from(cell.number_of_vertices()).is_err());
}
let err = Cell::facet_views_from_tds(dt.tds(), cell_key).unwrap_err();
assert!(matches!(
err,
FacetError::InvalidFacetIndex {
index: u8::MAX,
facet_count,
} if u8::try_from(facet_count).is_err()
));
let err = Cell::facet_view_iter(dt.tds(), cell_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 cell_deserialize_rejects_missing_uuid_and_duplicate_fields_and_invalid_uuid() {
let err = serde_json::from_str::<Cell<f64, (), i32, 3>>("null").unwrap_err();
assert!(err.to_string().contains("a Cell struct"));
let err = serde_json::from_str::<Cell<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::<Cell<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::<Cell<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::<Cell<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 cell = serde_json::from_str::<Cell<f64, (), i32, 3>>(&json).unwrap();
assert_eq!(cell.data, Some(5));
assert!(cell.neighbors.is_none());
assert_eq!(cell.number_of_vertices(), 0, "vertices are not serialized");
}
#[test]
fn cell_validation_error_from_stack_matrix_dispatch_error_maps_to_coordinate_conversion() {
use crate::geometry::matrix::{MAX_STACK_MATRIX_DIM, StackMatrixDispatchError};
let err = StackMatrixDispatchError::UnsupportedDim {
k: MAX_STACK_MATRIX_DIM + 1,
max: MAX_STACK_MATRIX_DIM,
};
let cell_err: CellValidationError = err.into();
assert!(matches!(
cell_err,
CellValidationError::CoordinateConversion { .. }
));
}
#[test]
fn test_cell_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 (_, cell_2d) = dt1.tds().cells().next().unwrap();
let dt2 = DelaunayTriangulation::new(&vertices_2d).unwrap();
let (_, cell_2d_copy) = dt2.tds().cells().next().unwrap();
assert_eq!(cell_2d, cell_2d_copy, "Identical 2D cells should be equal");
println!("✓ 2D cells 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 cell_key = dt.tds().cell_keys().next().unwrap();
let facet_iter =
Cell::facet_view_iter(dt.tds(), cell_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 =
Cell::facet_view_iter(dt.tds(), cell_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 =
Cell::facet_view_iter(dt.tds(), cell_key).expect("Failed to get third facet iterator");
let successful_facets: Vec<_> = facet_iter3.filter_map(Result::ok).collect();
assert_eq!(
successful_facets.len(),
4,
"All facets should be created successfully"
);
let vec_facets =
Cell::facet_views_from_tds(dt.tds(), cell_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]
#[cfg(feature = "bench")]
fn test_vertex_uuid_iter_by_value_vs_by_reference_analysis() {
use std::{collections::HashSet, mem, time::Instant};
use uuid::Uuid;
println!("\n=== UUID Performance Analysis: By Value vs By Reference ===");
println!("\nMemory Layout:");
println!(" Size of Uuid: {} bytes", mem::size_of::<Uuid>());
println!(" Size of &Uuid: {} bytes", mem::size_of::<&Uuid>());
println!(" Size of usize: {} bytes", mem::size_of::<usize>());
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 (_cell_key, cell) = dt.cells().next().unwrap();
println!("\nAPI Ergonomics Test:");
let first_uuid = cell.vertex_uuid_iter(dt.tds()).next().unwrap().unwrap();
assert_ne!(first_uuid, Uuid::nil());
println!(" ✓ By value: Direct comparison works: uuid != Uuid::nil()");
let uuid_values: Vec<Uuid> = cell
.vertices()
.iter()
.map(|&vkey| dt.tds().get_vertex_by_key(vkey).unwrap().uuid())
.collect();
let uuid_refs: Vec<&Uuid> = uuid_values.iter().collect();
let first_uuid_ref = uuid_refs[0];
assert_ne!(*first_uuid_ref, Uuid::nil()); println!(" ✓ By reference: Requires dereferencing: *uuid != Uuid::nil()");
println!("\nPerformance Test (1000 iterations):");
let iterations = 1000;
let start = Instant::now();
let mut by_value_count = 0;
for _ in 0..iterations {
for uuid in cell.vertex_uuid_iter(dt.tds()) {
if uuid.unwrap() != Uuid::nil() {
by_value_count += 1;
}
}
}
let by_value_time = start.elapsed();
let start = Instant::now();
let mut by_ref_count = 0;
for _ in 0..iterations {
let uuid_values: Vec<Uuid> = cell
.vertices()
.iter()
.map(|&vkey| dt.tds().get_vertex_by_key(vkey).unwrap().uuid())
.collect();
for uuid_ref in &uuid_values {
if *uuid_ref != Uuid::nil() {
by_ref_count += 1;
}
}
}
let by_ref_time = start.elapsed();
println!(" By value time: {by_value_time:?}");
println!(" By reference time: {by_ref_time:?}");
let ratio = by_ref_time.as_secs_f64() / by_value_time.as_secs_f64().max(f64::EPSILON);
if ratio > 1.05 {
println!(" → By value is {ratio:.2}x FASTER");
} else if ratio < 0.95 {
println!(" → By reference is {:.2}x faster", 1.0 / ratio);
} else {
println!(" → Performance is roughly equivalent");
}
assert_eq!(by_value_count, by_ref_count);
println!("\nAnalysis Summary:");
println!(
" UUID size: {} bytes (small value type)",
mem::size_of::<Uuid>()
);
println!(
" Reference size: {} bytes (pointer)",
mem::size_of::<&Uuid>()
);
println!(" \nFor UUIDs specifically:");
println!(" • UUID is only 16 bytes (fits in two 64-bit registers)");
println!(" • Modern CPUs copy 16 bytes very efficiently");
println!(" • No indirection overhead with by-value");
println!(" • Consumers can own the value (no lifetime constraints)");
println!(" • Direct comparisons work without dereferencing");
println!(" \nConclusion: Current by-value implementation is optimal");
println!(" Reasons:");
println!(" 1. Better or equivalent performance (no indirection)");
println!(" 2. Simpler API (no lifetime constraints)");
println!(" 3. More ergonomic for consumers (no dereferencing)");
println!(" 4. Consistent with UUID::new() and similar APIs");
println!(" 5. 16 bytes is small enough to copy efficiently");
assert_eq!(cell.vertex_uuid_iter(dt.tds()).count(), 4);
let unique_uuids: HashSet<_> = cell
.vertex_uuid_iter(dt.tds())
.collect::<Result<HashSet<_>, _>>()
.unwrap();
assert_eq!(unique_uuids.len(), 4);
println!(" ✓ Current API validation passed");
}
#[test]
fn test_cell_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.cells().next().unwrap().0;
assert_eq!(dt.tds().get_cell(key).unwrap().data(), None);
dt.set_cell_data(key, Some(99));
assert_eq!(dt.tds().get_cell(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 (_cell_key, original_cell) = dt.cells().next().unwrap();
let mut test_cell = original_cell.clone();
assert_eq!(
test_cell.number_of_vertices(),
4,
"Cell should start with 4 vertices"
);
test_cell.clear_vertex_keys();
assert_eq!(
test_cell.number_of_vertices(),
0,
"Cell should have 0 vertices after clearing"
);
assert_eq!(
test_cell.vertices().len(),
0,
"Vertices slice should be empty after clearing"
);
for &vkey in original_cell.vertices() {
test_cell.push_vertex_key(vkey);
}
assert_eq!(
test_cell.number_of_vertices(),
4,
"Cell should have 4 vertices after rebuilding"
);
for (original_vkey, rebuilt_vkey) in original_cell
.vertices()
.iter()
.zip(test_cell.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");
}
}