#![forbid(unsafe_code)]
use super::{
cell::{Cell, CellValidationError},
facet::{FacetHandle, facet_key_from_vertices},
traits::data_type::DataType,
util::{periodic_facet_key_from_lifted_vertices, usize_to_u8},
vertex::{Vertex, VertexValidationError},
};
use crate::core::collections::{
CellKeySet, CellRemovalBuffer, CellVerticesMap, Entry, FacetToCellsMap, FastHashMap,
MAX_PRACTICAL_DIMENSION_SIZE, NeighborBuffer, SmallBuffer, StorageMap, UuidToCellKeyMap,
UuidToVertexKeyMap, VertexKeyBuffer, VertexKeySet, fast_hash_map_with_capacity,
};
use crate::core::triangulation::TriangulationValidationError;
use crate::geometry::traits::coordinate::CoordinateScalar;
use serde::{
Deserialize, Deserializer, Serialize,
de::{self, MapAccess, Visitor},
};
use slotmap::{Key, new_key_type};
use std::{
cmp::Ordering as CmpOrdering,
collections::VecDeque,
fmt::{self, Debug},
marker::PhantomData,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
};
use thiserror::Error;
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TriangulationConstructionState {
Incomplete(usize),
Constructed,
}
impl Default for TriangulationConstructionState {
fn default() -> Self {
Self::Incomplete(0)
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TdsConstructionError {
#[error("Validation error during construction: {0}")]
ValidationError(#[from] TdsValidationError),
#[error("Duplicate UUID: {entity:?} with UUID {uuid} already exists")]
DuplicateUuid {
entity: EntityKind,
uuid: Uuid,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EntityKind {
Vertex,
Cell,
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TdsValidationError {
#[error("Invalid vertex {vertex_id}: {source}")]
InvalidVertex {
vertex_id: Uuid,
source: VertexValidationError,
},
#[error("Invalid cell {cell_id}: {source}")]
InvalidCell {
cell_id: Uuid,
source: CellValidationError,
},
#[error("Invalid neighbor relationships: {message}")]
InvalidNeighbors {
message: String,
},
#[error(
"Orientation invariant violated between cells {cell1_uuid} and {cell2_uuid}; shared facet orderings {facet_vertices:?} vs {cell2_facet_vertices:?} (cell1 facet index {cell1_facet_index}, cell2 facet index {cell2_facet_index}, observed_odd_permutation={observed_odd_permutation}, expected_odd_permutation={expected_odd_permutation})"
)]
OrientationViolation {
cell1_key: CellKey,
cell1_uuid: Uuid,
cell2_key: CellKey,
cell2_uuid: Uuid,
cell1_facet_index: usize,
cell2_facet_index: usize,
facet_vertices: Vec<VertexKey>,
cell2_facet_vertices: Vec<VertexKey>,
observed_odd_permutation: bool,
expected_odd_permutation: bool,
},
#[error("Duplicate cells detected: {message}")]
DuplicateCells {
message: String,
},
#[error("Failed to create cell: {message}")]
FailedToCreateCell {
message: String,
},
#[error("Cells {cell1:?} and {cell2:?} are not neighbors")]
NotNeighbors {
cell1: Uuid,
cell2: Uuid,
},
#[error("{entity:?} mapping inconsistency: {message}")]
MappingInconsistency {
entity: EntityKind,
message: String,
},
#[error("Failed to retrieve vertex keys for cell {cell_id}: {message}")]
VertexKeyRetrievalFailed {
cell_id: Uuid,
message: String,
},
#[error("Internal data structure inconsistency: {message}")]
InconsistentDataStructure {
message: String,
},
#[error("Insufficient vertices for {dimension}D triangulation: {source}")]
InsufficientVertices {
dimension: usize,
source: CellValidationError,
},
#[error("Facet operation failed: {0}")]
FacetError(#[from] super::facet::FacetError),
#[error("Finalization failed: {message}")]
FinalizationFailed {
message: String,
},
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[error(transparent)]
pub struct TdsMutationError(pub TdsValidationError);
impl From<TdsValidationError> for TdsMutationError {
fn from(err: TdsValidationError) -> Self {
Self(err)
}
}
impl From<TdsMutationError> for TdsValidationError {
fn from(err: TdsMutationError) -> Self {
err.0
}
}
type TdsError = TdsValidationError;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum InvariantKind {
VertexValidity,
CellValidity,
VertexMappings,
CellMappings,
CellVertexKeys,
VertexIncidence,
DuplicateCells,
FacetSharing,
NeighborConsistency,
CoherentOrientation,
Connectedness,
Topology,
DelaunayProperty,
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum InvariantError {
#[error(transparent)]
Tds(#[from] TdsValidationError),
#[error(transparent)]
Triangulation(#[from] TriangulationValidationError),
#[error(transparent)]
Delaunay(#[from] crate::core::delaunay_triangulation::DelaunayTriangulationValidationError),
}
#[derive(Clone, Debug)]
pub struct InvariantViolation {
pub kind: InvariantKind,
pub error: InvariantError,
}
#[derive(Clone, Debug)]
pub struct TriangulationValidationReport {
pub violations: Vec<InvariantViolation>,
}
impl TriangulationValidationReport {
#[must_use]
pub const fn is_empty(&self) -> bool {
self.violations.is_empty()
}
}
new_key_type! {
pub struct VertexKey;
}
new_key_type! {
pub struct CellKey;
}
#[derive(Clone, Debug)]
pub struct Tds<T, U, V, const D: usize>
where
U: DataType,
V: DataType,
{
vertices: StorageMap<VertexKey, Vertex<T, U, D>>,
cells: StorageMap<CellKey, Cell<T, U, V, D>>,
pub(crate) uuid_to_vertex_key: UuidToVertexKeyMap,
pub(crate) uuid_to_cell_key: UuidToCellKeyMap,
pub construction_state: TriangulationConstructionState,
generation: Arc<AtomicU64>,
}
impl<T, U, V, const D: usize> Tds<T, U, V, D>
where
U: DataType,
V: DataType,
{
#[inline]
fn allows_periodic_self_neighbor(cell: &Cell<T, U, V, D>) -> bool {
let Some(offsets) = cell.periodic_vertex_offsets() else {
return false;
};
!offsets.is_empty() && offsets.len() == cell.number_of_vertices()
}
fn periodic_facet_key_from_cell_vertices(
cell: &Cell<T, U, V, D>,
vertices: &[VertexKey],
facet_index: usize,
) -> Result<u64, TdsValidationError> {
if facet_index >= vertices.len() {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Facet index {facet_index} out of bounds for cell with {} vertices",
vertices.len()
),
});
}
let Some(periodic_offsets) = cell.periodic_vertex_offsets() else {
let mut facet_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::new();
for (i, &vertex_key) in vertices.iter().enumerate() {
if i != facet_index {
facet_vertices.push(vertex_key);
}
}
return Ok(facet_key_from_vertices(&facet_vertices));
};
if periodic_offsets.len() != vertices.len() {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Cell periodic offset count {} does not match vertex count {}",
periodic_offsets.len(),
vertices.len(),
),
});
}
let mut lifted_vertices: SmallBuffer<(VertexKey, [i8; D]), MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::new();
for (vertex_idx, &vertex_key) in vertices.iter().enumerate() {
lifted_vertices.push((vertex_key, periodic_offsets[vertex_idx]));
}
periodic_facet_key_from_lifted_vertices::<D>(&lifted_vertices, facet_index).map_err(
|error| TdsError::InconsistentDataStructure {
message: format!(
"Failed to derive periodic facet key for cell {:?} facet {facet_index}: {error}",
cell.uuid()
),
},
)
}
fn build_periodic_vertex_uuid_offsets(
&self,
cell_key: CellKey,
vertices: &[VertexKey],
) -> Result<Vec<(Uuid, [i8; D])>, TdsError> {
let cell = self
.cells
.get(cell_key)
.ok_or_else(|| TdsError::InconsistentDataStructure {
message: format!(
"Cell key {cell_key:?} missing while building periodic vertex key"
),
})?;
let periodic_offsets = cell.periodic_vertex_offsets();
if let Some(offsets) = periodic_offsets
&& offsets.len() != vertices.len()
{
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Cell {cell_key:?} periodic offset count {} does not match vertex count {}",
offsets.len(),
vertices.len(),
),
});
}
let mut vertex_uuid_offsets: Vec<(Uuid, [i8; D])> = Vec::with_capacity(vertices.len());
for (vertex_idx, &vertex_key) in vertices.iter().enumerate() {
let vertex = self
.vertices
.get(vertex_key)
.ok_or_else(|| TdsError::InconsistentDataStructure {
message: format!(
"Cell {cell_key:?} references missing vertex key {vertex_key:?} while building periodic vertex key at index {vertex_idx}",
),
})?;
let offset = periodic_offsets.map_or([0_i8; D], |offsets| offsets[vertex_idx]);
vertex_uuid_offsets.push((vertex.uuid(), offset));
}
vertex_uuid_offsets.sort_unstable_by_key(|(uuid, _)| *uuid);
Ok(vertex_uuid_offsets)
}
pub(crate) fn facet_key_for_cell_facet(
&self,
cell_key: CellKey,
facet_index: usize,
) -> Result<u64, TdsValidationError> {
let vertices = self.get_cell_vertices(cell_key)?;
let cell = self
.cells
.get(cell_key)
.ok_or_else(|| TdsError::InconsistentDataStructure {
message: format!(
"Cell key {cell_key:?} not found while deriving facet key for index {facet_index}",
),
})?;
Self::periodic_facet_key_from_cell_vertices(cell, &vertices, facet_index)
}
fn assign_neighbors(&mut self) -> Result<(), TdsValidationError> {
type FacetInfo = (CellKey, usize);
let cap = self.cells.len().saturating_mul(D.saturating_add(1));
let mut facet_map: FastHashMap<u64, SmallBuffer<FacetInfo, 2>> =
fast_hash_map_with_capacity(cap);
for (cell_key, cell) in &self.cells {
let vertices = self.get_cell_vertices(cell_key).map_err(|err| {
TdsError::VertexKeyRetrievalFailed {
cell_id: cell.uuid(),
message: format!(
"Failed to retrieve vertex keys for cell during neighbor assignment: {err}"
),
}
})?;
for i in 0..vertices.len() {
let facet_key = Self::periodic_facet_key_from_cell_vertices(cell, &vertices, i)?;
let facet_entry = facet_map.entry(facet_key).or_default();
if facet_entry.len() >= 2 {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Facet with key {} already shared by {} cells; cannot add cell {} (would violate 2-manifold property)",
facet_key,
facet_entry.len(),
cell.uuid()
),
});
}
facet_entry.push((cell_key, i));
}
}
let mut cell_neighbors: FastHashMap<
CellKey,
SmallBuffer<Option<CellKey>, MAX_PRACTICAL_DIMENSION_SIZE>,
> = fast_hash_map_with_capacity(self.cells.len());
for (cell_key, cell) in &self.cells {
let vertex_count = cell.number_of_vertices();
if vertex_count > MAX_PRACTICAL_DIMENSION_SIZE {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Cell {} has {} vertices, which exceeds MAX_PRACTICAL_DIMENSION_SIZE={}. \
This would overflow the neighbors buffer.",
cell.uuid(),
vertex_count,
MAX_PRACTICAL_DIMENSION_SIZE
),
});
}
let mut neighbors = SmallBuffer::with_capacity(vertex_count);
neighbors.resize(vertex_count, None);
cell_neighbors.insert(cell_key, neighbors);
}
for (_facet_key, facet_infos) in facet_map {
if facet_infos.len() != 2 {
continue;
}
let (cell_key1, vertex_index1) = facet_infos[0];
let (cell_key2, vertex_index2) = facet_infos[1];
cell_neighbors.get_mut(&cell_key1).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!("Cell key {cell_key1:?} not found in cell neighbors map"),
}
})?[vertex_index1] = Some(cell_key2);
cell_neighbors.get_mut(&cell_key2).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!("Cell key {cell_key2:?} not found in cell neighbors map"),
}
})?[vertex_index2] = Some(cell_key1);
}
for (cell_key, neighbors) in &cell_neighbors {
if let Some(cell) = self.cells.get_mut(*cell_key) {
if neighbors.iter().all(Option::is_none) {
cell.neighbors = None;
} else {
let mut neighbor_buffer = SmallBuffer::new();
neighbor_buffer.extend(neighbors.iter().copied());
cell.neighbors = Some(neighbor_buffer);
}
}
}
self.bump_generation();
Ok(())
}
pub fn cells(&self) -> impl Iterator<Item = (CellKey, &Cell<T, U, V, D>)> {
self.cells.iter()
}
pub fn cells_values(&self) -> impl Iterator<Item = &Cell<T, U, V, D>> {
self.cells.values()
}
pub fn vertices(&self) -> impl Iterator<Item = (VertexKey, &Vertex<T, U, D>)> {
self.vertices.iter()
}
pub fn vertex_keys(&self) -> impl Iterator<Item = VertexKey> + '_ {
self.vertices.keys()
}
pub fn cell_keys(&self) -> impl Iterator<Item = CellKey> + '_ {
self.cells.keys()
}
#[must_use]
pub fn get_cell(&self, key: CellKey) -> Option<&Cell<T, U, V, D>> {
self.cells.get(key)
}
#[must_use]
pub fn contains_cell(&self, key: CellKey) -> bool {
self.cells.contains_key(key)
}
#[must_use]
pub fn number_of_vertices(&self) -> usize {
self.vertices.len()
}
#[must_use]
pub fn dim(&self) -> i32 {
let nv = self.number_of_vertices();
let nv_i32 = i32::try_from(nv).unwrap_or(i32::MAX);
let d_i32 = i32::try_from(D).unwrap_or(i32::MAX);
nv_i32.saturating_sub(1).min(d_i32)
}
#[must_use]
pub fn number_of_cells(&self) -> usize {
self.cells.len()
}
#[must_use]
pub fn is_connected(&self) -> bool {
let total = self.cells.len();
if total == 0 {
return true;
}
let Some(start) = self.cell_keys().next() else {
return true;
};
let mut visited: CellKeySet = CellKeySet::default();
visited.reserve(total);
let mut stack: Vec<CellKey> = Vec::with_capacity(total.min(64));
stack.push(start);
while let Some(ck) = stack.pop() {
if !visited.insert(ck) {
continue;
}
let Some(cell) = self.cells.get(ck) else {
continue;
};
let Some(neighbors) = cell.neighbors() else {
continue;
};
for &n_opt in neighbors {
let Some(nk) = n_opt else {
continue;
};
if self.cells.contains_key(nk) && !visited.contains(&nk) {
stack.push(nk);
}
}
}
visited.len() == total
}
#[inline]
fn bump_generation(&self) {
self.generation.fetch_add(1, Ordering::Relaxed);
}
#[inline]
#[must_use]
pub fn generation(&self) -> u64 {
self.generation.load(Ordering::Relaxed)
}
#[inline]
pub(crate) fn mark_topology_modified(&self) {
self.bump_generation();
}
#[doc(hidden)]
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "Dangerous internal API used only in tests to intentionally violate invariants",
)
)]
pub(crate) const fn cells_mut(&mut self) -> &mut StorageMap<CellKey, Cell<T, U, V, D>> {
&mut self.cells
}
pub(crate) fn insert_vertex_with_mapping(
&mut self,
vertex: Vertex<T, U, D>,
) -> Result<VertexKey, TdsConstructionError> {
let vertex_uuid = vertex.uuid();
match self.uuid_to_vertex_key.entry(vertex_uuid) {
Entry::Occupied(_) => Err(TdsConstructionError::DuplicateUuid {
entity: EntityKind::Vertex,
uuid: vertex_uuid,
}),
Entry::Vacant(e) => {
let vertex_key = self.vertices.insert(vertex);
e.insert(vertex_key);
self.bump_generation();
Ok(vertex_key)
}
}
}
pub(crate) fn insert_cell_with_mapping(
&mut self,
cell: Cell<T, U, V, D>,
) -> Result<CellKey, TdsConstructionError> {
debug_assert_eq!(
cell.number_of_vertices(),
D + 1,
"Cell should have exactly D+1 vertices for quick failure in dev"
);
if cell.number_of_vertices() != D + 1 {
return Err(TdsConstructionError::ValidationError(
TdsError::InconsistentDataStructure {
message: format!(
"Cell must have exactly {} vertices for {}-dimensional simplex, but has {}",
D + 1,
D,
cell.number_of_vertices()
),
},
));
}
for &vkey in cell.vertices() {
if !self.vertices.contains_key(vkey) {
return Err(TdsConstructionError::ValidationError(
TdsError::InconsistentDataStructure {
message: format!(
"Cell references vertex key {vkey:?} that does not exist in the triangulation"
),
},
));
}
}
let cell_uuid = cell.uuid();
match self.uuid_to_cell_key.entry(cell_uuid) {
Entry::Occupied(_) => Err(TdsConstructionError::DuplicateUuid {
entity: EntityKind::Cell,
uuid: cell_uuid,
}),
Entry::Vacant(e) => {
let cell_key = self.cells.insert(cell);
e.insert(cell_key);
self.bump_generation();
Ok(cell_key)
}
}
}
#[inline]
pub fn get_cell_vertices(
&self,
cell_key: CellKey,
) -> Result<VertexKeyBuffer, TdsValidationError> {
let cell = self
.cells
.get(cell_key)
.ok_or_else(|| TdsError::InconsistentDataStructure {
message: format!("Cell key {cell_key:?} not found in cells storage map"),
})?;
let cell_vertices = cell.vertices();
let mut keys = VertexKeyBuffer::with_capacity(cell_vertices.len());
for (idx, &vertex_key) in cell_vertices.iter().enumerate() {
if !self.vertices.contains_key(vertex_key) {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Cell {} (key {cell_key:?}) references non-existent vertex key {vertex_key:?} at position {idx}",
cell.uuid()
),
});
}
keys.push(vertex_key);
}
Ok(keys)
}
#[inline]
#[must_use]
pub fn cell_key_from_uuid(&self, cell_uuid: &Uuid) -> Option<CellKey> {
self.uuid_to_cell_key.get(cell_uuid).copied()
}
#[inline]
#[must_use]
pub fn vertex_key_from_uuid(&self, vertex_uuid: &Uuid) -> Option<VertexKey> {
self.uuid_to_vertex_key.get(vertex_uuid).copied()
}
#[inline]
#[must_use]
pub fn cell_uuid_from_key(&self, cell_key: CellKey) -> Option<Uuid> {
self.cells.get(cell_key).map(super::cell::Cell::uuid)
}
#[inline]
#[must_use]
pub fn vertex_uuid_from_key(&self, vertex_key: VertexKey) -> Option<Uuid> {
self.vertices
.get(vertex_key)
.map(super::vertex::Vertex::uuid)
}
#[inline]
#[must_use]
pub fn get_cell_by_key_mut(&mut self, cell_key: CellKey) -> Option<&mut Cell<T, U, V, D>> {
self.cells.get_mut(cell_key)
}
#[inline]
#[must_use]
pub fn get_vertex_by_key(&self, vertex_key: VertexKey) -> Option<&Vertex<T, U, D>> {
self.vertices.get(vertex_key)
}
#[inline]
#[must_use]
pub fn get_vertex_by_key_mut(&mut self, vertex_key: VertexKey) -> Option<&mut Vertex<T, U, D>> {
self.vertices.get_mut(vertex_key)
}
#[inline]
#[must_use]
pub fn contains_cell_key(&self, cell_key: CellKey) -> bool {
self.cells.contains_key(cell_key)
}
#[inline]
#[must_use]
pub fn contains_vertex_key(&self, vertex_key: VertexKey) -> bool {
self.vertices.contains_key(vertex_key)
}
pub fn remove_cell_by_key(&mut self, cell_key: CellKey) -> Option<Cell<T, U, V, D>> {
if let Some(removed_cell) = self.cells.remove(cell_key) {
self.uuid_to_cell_key.remove(&removed_cell.uuid());
self.bump_generation();
Some(removed_cell)
} else {
None
}
}
pub fn remove_cells_by_keys(&mut self, cell_keys: &[CellKey]) -> usize {
if cell_keys.is_empty() {
return 0;
}
let cells_to_remove: CellKeySet = cell_keys.iter().copied().collect();
let (affected_vertices, candidate_incident) = self
.collect_removal_frontier_and_clear_neighbor_back_references(
cell_keys,
&cells_to_remove,
);
let removed_count = self.remove_cells_and_update_uuid_mappings(cell_keys);
if removed_count == 0 {
return 0;
}
self.repair_incident_cells_after_cell_removal(
&affected_vertices,
&cells_to_remove,
&candidate_incident,
);
self.bump_generation();
removed_count
}
#[cfg(test)]
pub(crate) fn repair_degenerate_cells(&mut self) -> usize {
if self.cells.is_empty() {
return 0;
}
let mut to_remove: Vec<CellKey> = Vec::new();
for (cell_key, cell) in self.cells() {
if cell.is_valid().is_err() {
to_remove.push(cell_key);
continue;
}
if cell
.vertices()
.iter()
.any(|&vkey| !self.vertices.contains_key(vkey))
{
to_remove.push(cell_key);
continue;
}
if cell.neighbors().is_some_and(|neighbors| {
neighbors
.iter()
.flatten()
.any(|&neighbor_key| !self.cells.contains_key(neighbor_key))
}) {
to_remove.push(cell_key);
}
}
if to_remove.is_empty() {
return 0;
}
self.remove_cells_by_keys(&to_remove)
}
fn collect_removal_frontier_and_clear_neighbor_back_references(
&mut self,
cell_keys: &[CellKey],
cells_to_remove: &CellKeySet,
) -> (VertexKeySet, FastHashMap<VertexKey, CellKey>) {
let mut affected_vertices: VertexKeySet = VertexKeySet::default();
let mut candidate_incident: FastHashMap<VertexKey, CellKey> =
fast_hash_map_with_capacity(cell_keys.len().saturating_mul(D.saturating_add(1)));
for &cell_key in cell_keys {
let Some((vertices, neighbors)) = self.cells.get(cell_key).map(|cell| {
let mut vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(cell.number_of_vertices());
vertices.extend(cell.vertices().iter().copied());
let mut neighbors: SmallBuffer<Option<CellKey>, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(vertices.len());
neighbors.resize(vertices.len(), None);
if let Some(cell_neighbors) = cell.neighbors() {
for (slot, neighbor_opt) in neighbors.iter_mut().zip(cell_neighbors.iter()) {
*slot = *neighbor_opt;
}
}
(vertices, neighbors)
}) else {
continue;
};
for &vk in &vertices {
affected_vertices.insert(vk);
}
for (facet_idx, neighbor_key_opt) in neighbors.iter().enumerate() {
let Some(neighbor_key) = neighbor_key_opt else {
continue;
};
if cells_to_remove.contains(neighbor_key) {
continue; }
for (vertex_index, &vkey) in vertices.iter().enumerate() {
if vertex_index == facet_idx {
continue;
}
candidate_incident.entry(vkey).or_insert(*neighbor_key);
}
let Some(neighbor_cell) = self.cells.get_mut(*neighbor_key) else {
continue;
};
let Some(neighbors_buf) = neighbor_cell.neighbors.as_mut() else {
continue;
};
for slot in neighbors_buf.iter_mut() {
if *slot == Some(cell_key) {
*slot = None;
}
}
if neighbors_buf.iter().all(Option::is_none) {
neighbor_cell.neighbors = None;
}
}
}
(affected_vertices, candidate_incident)
}
fn remove_cells_and_update_uuid_mappings(&mut self, cell_keys: &[CellKey]) -> usize {
let mut removed_count = 0;
for &cell_key in cell_keys {
if let Some(removed_cell) = self.cells.remove(cell_key) {
self.uuid_to_cell_key.remove(&removed_cell.uuid());
removed_count += 1;
}
}
removed_count
}
fn repair_incident_cells_after_cell_removal(
&mut self,
affected_vertices: &VertexKeySet,
cells_to_remove: &CellKeySet,
candidate_incident: &FastHashMap<VertexKey, CellKey>,
) {
let vertices_to_repair: Vec<VertexKey> = affected_vertices
.iter()
.copied()
.filter(|&vk| {
let Some(v) = self.vertices.get(vk) else {
return false;
};
match v.incident_cell {
None => true,
Some(cell_key) => {
cells_to_remove.contains(&cell_key) || !self.cells.contains_key(cell_key)
}
}
})
.collect();
let mut incident_updates: Vec<(VertexKey, Option<CellKey>)> =
Vec::with_capacity(vertices_to_repair.len());
for vk in vertices_to_repair {
let mut new_incident = candidate_incident.get(&vk).copied().filter(|&ck| {
self.cells
.get(ck)
.is_some_and(|cell| cell.contains_vertex(vk))
});
if new_incident.is_none() {
new_incident = self
.cells
.iter()
.find_map(|(cell_key, cell)| cell.contains_vertex(vk).then_some(cell_key));
}
incident_updates.push((vk, new_incident));
}
for (vk, new_incident) in incident_updates {
if let Some(vertex) = self.vertices.get_mut(vk) {
vertex.incident_cell = new_incident;
}
}
}
fn find_cells_containing_vertex(&self, vertex_key: VertexKey) -> CellRemovalBuffer {
let fallback_scan = || {
self.cells()
.filter_map(|(cell_key, cell)| cell.contains_vertex(vertex_key).then_some(cell_key))
.collect()
};
let Some(vertex) = self.vertices.get(vertex_key) else {
return fallback_scan();
};
let Some(start_cell_key) = vertex.incident_cell else {
return fallback_scan();
};
let Some(start_cell) = self.cells.get(start_cell_key) else {
return fallback_scan();
};
if !start_cell.contains_vertex(vertex_key) || start_cell.neighbors().is_none() {
return fallback_scan();
}
let mut visited: CellKeySet = CellKeySet::default();
let mut stack: CellRemovalBuffer = CellRemovalBuffer::new();
let mut result: CellRemovalBuffer = CellRemovalBuffer::new();
visited.insert(start_cell_key);
stack.push(start_cell_key);
while let Some(cell_key) = stack.pop() {
result.push(cell_key);
let Some(cell) = self.cells.get(cell_key) else {
continue;
};
let Some(neighbors) = cell.neighbors() else {
continue;
};
for (facet_idx, neighbor_opt) in neighbors.iter().enumerate() {
if cell
.vertices()
.get(facet_idx)
.is_some_and(|&vkey| vkey == vertex_key)
{
continue;
}
let Some(neighbor_key) = neighbor_opt else {
continue;
};
if visited.contains(neighbor_key) {
continue;
}
let Some(neighbor_cell) = self.cells.get(*neighbor_key) else {
continue;
};
if !neighbor_cell.contains_vertex(vertex_key) {
continue;
}
visited.insert(*neighbor_key);
stack.push(*neighbor_key);
}
}
result
}
#[expect(
clippy::unnecessary_wraps,
reason = "Keep Result for future mutation validation without changing the API"
)]
pub(crate) fn remove_vertex(
&mut self,
vertex: &Vertex<T, U, D>,
) -> Result<usize, TdsMutationError> {
let Some(vertex_key) = self.vertex_key_from_uuid(&vertex.uuid()) else {
return Ok(0); };
let cells_to_remove = self.find_cells_containing_vertex(vertex_key);
let cells_removed = self.remove_cells_by_keys(&cells_to_remove);
self.vertices.remove(vertex_key);
self.uuid_to_vertex_key.remove(&vertex.uuid());
self.bump_generation();
Ok(cells_removed)
}
#[must_use]
pub fn find_neighbors_by_key(&self, cell_key: CellKey) -> NeighborBuffer<Option<CellKey>> {
let mut neighbors = NeighborBuffer::new();
neighbors.resize(D + 1, None);
let Some(cell) = self.get_cell(cell_key) else {
return neighbors;
};
if let Some(ref neighbors_from_cell) = cell.neighbors {
for (slot, neighbor_key_opt) in neighbors.iter_mut().zip(neighbors_from_cell.iter()) {
*slot = *neighbor_key_opt;
}
}
neighbors
}
fn validate_neighbor_topology(
&self,
cell_key: CellKey,
neighbors: &[Option<CellKey>],
) -> Result<(), TdsValidationError> {
if neighbors.len() != D + 1 {
return Err(TdsError::InvalidNeighbors {
message: format!(
"Neighbor vector length {} != D+1 ({})",
neighbors.len(),
D + 1
),
});
}
let cell = self
.cells
.get(cell_key)
.ok_or_else(|| TdsError::InconsistentDataStructure {
message: format!("Cell key {cell_key:?} not found"),
})?;
let cell_vertices = cell.vertices();
for (i, neighbor_key_opt) in neighbors.iter().enumerate() {
if let Some(neighbor_key) = neighbor_key_opt {
if *neighbor_key == cell_key {
if Self::allows_periodic_self_neighbor(cell) {
continue;
}
return Err(TdsError::InvalidNeighbors {
message: format!(
"Cell {:?} has non-periodic self-neighbor at position {i}; self-adjacency is only valid for explicitly periodic cells",
cell.uuid(),
),
});
}
let neighbor = self.cells.get(*neighbor_key).ok_or_else(|| {
TdsError::InvalidNeighbors {
message: format!(
"Neighbor at position {i} references non-existent cell {neighbor_key:?}"
),
}
})?;
let neighbor_vertices = neighbor.vertices();
let mut shared_count = 0;
let mut missing_vertex_idx = None;
for (idx, &vkey) in cell_vertices.iter().enumerate() {
if neighbor_vertices.contains(&vkey) {
shared_count += 1;
} else if missing_vertex_idx.is_none() {
missing_vertex_idx = Some(idx);
}
}
if shared_count != D {
return Err(TdsError::InvalidNeighbors {
message: format!(
"Cell {:?} neighbor at position {i} shares {shared_count} vertices, expected {D}. \
Invariant: neighbor[{i}] must share facet opposite vertex[{i}] (all vertices except vertex {i})",
cell.uuid()
),
});
}
if missing_vertex_idx != Some(i) {
return Err(TdsError::InvalidNeighbors {
message: format!(
"Cell {:?} neighbor at position {i} is opposite vertex {:?}, expected {i}. \
Invariant: neighbor[{i}] must be opposite vertex[{i}]",
cell.uuid(),
missing_vertex_idx
),
});
}
}
}
Ok(())
}
pub fn set_neighbors_by_key(
&mut self,
cell_key: CellKey,
neighbors: &[Option<CellKey>],
) -> Result<(), TdsMutationError> {
self.validate_neighbor_topology(cell_key, neighbors)?;
let neighbors_vec = neighbors;
let cell = self.get_cell_by_key_mut(cell_key).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!("Cell with key {cell_key:?} not found"),
}
})?;
if neighbors_vec.iter().all(Option::is_none) {
cell.neighbors = None;
} else {
let mut neighbor_buffer = SmallBuffer::new();
neighbor_buffer.extend(neighbors_vec.iter().copied());
cell.neighbors = Some(neighbor_buffer);
}
self.bump_generation();
Ok(())
}
#[must_use]
pub fn find_cells_containing_vertex_by_key(&self, vertex_key: VertexKey) -> CellKeySet {
if self.get_vertex_by_key(vertex_key).is_none() {
return CellKeySet::default();
}
let cells = self.find_cells_containing_vertex(vertex_key);
cells.iter().copied().collect()
}
pub fn assign_incident_cells(&mut self) -> Result<(), TdsMutationError> {
if self.cells.is_empty() {
for vertex in self.vertices.values_mut() {
vertex.incident_cell = None;
}
return Ok(());
}
for vertex in self.vertices.values_mut() {
vertex.incident_cell = None;
}
for (cell_key, cell) in &self.cells {
for &vertex_key in cell.vertices() {
let vertex = self.vertices.get_mut(vertex_key).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!(
"Vertex key {vertex_key:?} not found in vertices storage map during incident cell assignment"
),
}
})?;
if vertex.incident_cell.is_none() {
vertex.incident_cell = Some(cell_key);
}
}
}
Ok(())
}
#[must_use]
pub fn empty() -> Self {
Self {
vertices: StorageMap::with_key(),
cells: StorageMap::with_key(),
uuid_to_vertex_key: UuidToVertexKeyMap::default(),
uuid_to_cell_key: UuidToCellKeyMap::default(),
construction_state: TriangulationConstructionState::Incomplete(0),
generation: Arc::new(AtomicU64::new(0)),
}
}
#[inline]
pub fn clear_all_neighbors(&mut self) {
for cell in self.cells.values_mut() {
cell.clear_neighbors();
}
self.bump_generation();
}
pub(crate) fn normalize_coherent_orientation(&mut self) -> Result<(), TdsValidationError> {
let mut flip_assignment: FastHashMap<CellKey, bool> =
fast_hash_map_with_capacity(self.cells.len());
for root_cell_key in self.cells.keys() {
if flip_assignment.contains_key(&root_cell_key) {
continue;
}
flip_assignment.insert(root_cell_key, false);
let mut queue = VecDeque::new();
queue.push_back(root_cell_key);
while let Some(cell_key) = queue.pop_front() {
let this_flip_state = *flip_assignment.get(&cell_key).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!(
"Missing flip assignment for cell {cell_key:?} during orientation normalization",
),
}
})?;
let cell = self.cells.get(cell_key).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!(
"Cell {cell_key:?} missing during orientation normalization traversal",
),
}
})?;
let Some(neighbors) = cell.neighbors() else {
continue;
};
for (facet_idx, neighbor_key_opt) in neighbors.iter().enumerate() {
let Some(neighbor_key) = *neighbor_key_opt else {
continue;
};
if neighbor_key == cell_key && Self::allows_periodic_self_neighbor(cell) {
continue;
}
let neighbor_cell = self.cells.get(neighbor_key).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!(
"Neighbor cell {neighbor_key:?} referenced by {cell_key:?} missing during orientation normalization",
),
}
})?;
if cell.periodic_vertex_offsets().is_some()
|| neighbor_cell.periodic_vertex_offsets().is_some()
{
continue;
}
let mirror_idx = cell.mirror_facet_index(facet_idx, neighbor_cell).ok_or_else(
|| TdsError::InvalidNeighbors {
message: format!(
"Could not determine mirror facet while normalizing orientation: cell {:?}[{facet_idx}] -> neighbor {:?}",
cell.uuid(),
neighbor_cell.uuid(),
),
},
)?;
let (currently_coherent, _, _) =
Self::facet_permutation_parity(cell, facet_idx, neighbor_cell, mirror_idx)?;
let requires_relative_flip = !currently_coherent;
let required_neighbor_flip_state = this_flip_state ^ requires_relative_flip;
if let Some(existing_neighbor_flip_state) = flip_assignment.get(&neighbor_key) {
if *existing_neighbor_flip_state != required_neighbor_flip_state {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Contradictory orientation constraints while normalizing cells {:?} and {:?}",
cell.uuid(),
neighbor_cell.uuid(),
),
});
}
} else {
flip_assignment.insert(neighbor_key, required_neighbor_flip_state);
queue.push_back(neighbor_key);
}
}
}
}
let mut flipped_any = false;
for (cell_key, should_flip) in flip_assignment {
if !should_flip {
continue;
}
let cell = self.cells.get_mut(cell_key).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!(
"Cell {cell_key:?} missing while applying orientation normalization",
),
}
})?;
if cell.number_of_vertices() >= 2 {
cell.swap_vertex_slots(0, 1);
flipped_any = true;
}
}
if flipped_any {
self.bump_generation();
}
Ok(())
}
pub fn build_facet_to_cells_map(&self) -> Result<FacetToCellsMap, TdsValidationError> {
debug_assert!(
D <= 255,
"Dimension D must be <= 255 to fit facet indices in u8 (indices 0..=D)"
);
let cap = self.cells.len().saturating_mul(D.saturating_add(1));
let mut facet_to_cells: FacetToCellsMap = fast_hash_map_with_capacity(cap);
for (cell_id, cell) in &self.cells {
let vertices = self.get_cell_vertices(cell_id)?;
for i in 0..vertices.len() {
let facet_key = Self::periodic_facet_key_from_cell_vertices(cell, &vertices, i)?;
let Ok(facet_index_u8) = usize_to_u8(i, vertices.len()) else {
return Err(TdsError::InconsistentDataStructure {
message: format!("Facet index {i} exceeds u8 range for dimension {D}"),
});
};
facet_to_cells
.entry(facet_key)
.or_default()
.push(FacetHandle::new(cell_id, facet_index_u8));
}
}
Ok(facet_to_cells)
}
pub fn remove_duplicate_cells(&mut self) -> Result<usize, TdsMutationError> {
let mut unique_cells = FastHashMap::default();
let mut cells_to_remove = CellRemovalBuffer::new();
for cell_key in self.cells.keys() {
let vertices = self.get_cell_vertices(cell_key)?;
let vertex_uuid_offsets =
self.build_periodic_vertex_uuid_offsets(cell_key, &vertices)?;
match unique_cells.entry(vertex_uuid_offsets) {
Entry::Occupied(_) => {
cells_to_remove.push(cell_key);
}
Entry::Vacant(e) => {
e.insert(cell_key);
}
}
}
let duplicate_count = cells_to_remove.len();
for cell_key in &cells_to_remove {
if let Some(removed_cell) = self.cells.remove(*cell_key) {
self.uuid_to_cell_key.remove(&removed_cell.uuid());
}
}
if duplicate_count > 0 {
self.assign_neighbors()?;
self.assign_incident_cells()?;
}
Ok(duplicate_count)
}
fn validate_vertex_mappings(&self) -> Result<(), TdsValidationError> {
if self.uuid_to_vertex_key.len() != self.vertices.len() {
return Err(TdsError::MappingInconsistency {
entity: EntityKind::Vertex,
message: format!(
"Number of mapping entries ({}) doesn't match number of vertices ({})",
self.uuid_to_vertex_key.len(),
self.vertices.len()
),
});
}
for (vertex_key, vertex) in &self.vertices {
let vertex_uuid = vertex.uuid();
if self.vertex_uuid_from_key(vertex_key) != Some(vertex_uuid) {
return Err(TdsError::MappingInconsistency {
entity: EntityKind::Vertex,
message: format!(
"Inconsistent or missing key-to-UUID mapping for key {vertex_key:?}"
),
});
}
if self.uuid_to_vertex_key.get(&vertex_uuid) != Some(&vertex_key) {
return Err(TdsError::MappingInconsistency {
entity: EntityKind::Vertex,
message: format!(
"Inconsistent or missing UUID-to-key mapping for UUID {vertex_uuid:?}"
),
});
}
}
Ok(())
}
fn validate_cell_mappings(&self) -> Result<(), TdsValidationError> {
if self.uuid_to_cell_key.len() != self.cells.len() {
return Err(TdsError::MappingInconsistency {
entity: EntityKind::Cell,
message: format!(
"Number of mapping entries ({}) doesn't match number of cells ({})",
self.uuid_to_cell_key.len(),
self.cells.len()
),
});
}
for (cell_key, cell) in &self.cells {
let cell_uuid = cell.uuid();
if self.cell_uuid_from_key(cell_key) != Some(cell_uuid) {
return Err(TdsError::MappingInconsistency {
entity: EntityKind::Cell,
message: format!(
"Inconsistent or missing key-to-UUID mapping for key {cell_key:?}"
),
});
}
if self.uuid_to_cell_key.get(&cell_uuid) != Some(&cell_key) {
return Err(TdsError::MappingInconsistency {
entity: EntityKind::Cell,
message: format!(
"Inconsistent or missing UUID-to-key mapping for UUID {cell_uuid:?}"
),
});
}
}
Ok(())
}
fn validate_cell_vertex_keys(&self) -> Result<(), TdsValidationError> {
for (cell_key, cell) in &self.cells {
let cell_uuid = cell.uuid();
for (vertex_idx, &vertex_key) in cell.vertices().iter().enumerate() {
if !self.vertices.contains_key(vertex_key) {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Cell {cell_uuid} (key {cell_key:?}) references non-existent vertex key {vertex_key:?} at position {vertex_idx}"
),
});
}
}
}
Ok(())
}
fn validate_vertex_incidence(&self) -> Result<(), TdsValidationError> {
for (vertex_key, vertex) in &self.vertices {
let Some(incident_cell_key) = vertex.incident_cell else {
continue;
};
let Some(incident_cell) = self.cells.get(incident_cell_key) else {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Vertex {vertex_key:?} has dangling incident_cell pointer to missing cell {incident_cell_key:?}"
),
});
};
if !incident_cell.contains_vertex(vertex_key) {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Vertex {vertex_key:?} incident_cell {incident_cell_key:?} does not contain the vertex"
),
});
}
}
Ok(())
}
fn validate_no_duplicate_cells(&self) -> Result<(), TdsValidationError> {
let mut unique_cells: FastHashMap<Vec<(Uuid, [i8; D])>, CellKey> =
crate::core::collections::fast_hash_map_with_capacity(self.cells.len());
let mut duplicates = Vec::new();
for (cell_key, _cell) in &self.cells {
let vertices = self.get_cell_vertices(cell_key)?;
let vertex_uuid_offsets =
self.build_periodic_vertex_uuid_offsets(cell_key, &vertices)?;
if let Some(existing_cell_key) = unique_cells.get(&vertex_uuid_offsets) {
duplicates.push((cell_key, *existing_cell_key, vertex_uuid_offsets.clone()));
} else {
unique_cells.insert(vertex_uuid_offsets, cell_key);
}
}
if !duplicates.is_empty() {
let duplicate_descriptions: Vec<String> = duplicates
.iter()
.map(|(cell1, cell2, vertex_uuids)| {
format!("cells {cell1:?} and {cell2:?} with vertex UUIDs {vertex_uuids:?}")
})
.collect();
return Err(TdsError::DuplicateCells {
message: format!(
"Found {} duplicate cell(s): {}",
duplicates.len(),
duplicate_descriptions.join(", ")
),
});
}
Ok(())
}
pub(crate) fn validate_facet_sharing(&self) -> Result<(), TdsValidationError> {
let facet_to_cells = self.build_facet_to_cells_map()?;
Self::validate_facet_sharing_with_facet_to_cells_map(&facet_to_cells)
}
#[must_use]
pub fn is_coherently_oriented(&self) -> bool {
self.validate_coherent_orientation().is_ok()
}
fn validate_coherent_orientation(&self) -> Result<(), TdsValidationError> {
for (cell_key, cell) in &self.cells {
let Some(neighbors) = cell.neighbors() else {
continue;
};
for (facet_idx, neighbor_key_opt) in neighbors.iter().enumerate() {
let Some(neighbor_key) = *neighbor_key_opt else {
continue; };
if neighbor_key == cell_key && Self::allows_periodic_self_neighbor(cell) {
continue;
}
let neighbor_cell =
self.cells
.get(neighbor_key)
.ok_or_else(|| TdsError::InconsistentDataStructure {
message: format!(
"Neighbor cell {neighbor_key:?} referenced by cell {cell_key:?} is missing during orientation validation",
),
})?;
let mirror_idx = cell.mirror_facet_index(facet_idx, neighbor_cell).ok_or_else(
|| TdsError::InvalidNeighbors {
message: format!(
"Could not determine mirror facet: cell {:?}[{facet_idx}] -> neighbor {:?}",
cell.uuid(),
neighbor_cell.uuid(),
),
},
)?;
let back_neighbor = neighbor_cell
.neighbors()
.and_then(|neighbor_neighbors| {
neighbor_neighbors.get(mirror_idx).copied().flatten()
})
.ok_or_else(|| TdsError::InvalidNeighbors {
message: format!(
"Orientation validation expected back-reference: cell {:?}[{facet_idx}] -> {:?} should be mirrored by cell {:?}[{mirror_idx}] -> {:?}, found None",
cell.uuid(),
neighbor_key,
neighbor_cell.uuid(),
cell_key,
),
})?;
if back_neighbor != cell_key {
return Err(TdsError::InvalidNeighbors {
message: format!(
"Orientation validation expected back-reference: cell {:?}[{facet_idx}] -> {:?} should be mirrored by cell {:?}[{mirror_idx}] -> {:?}, found {:?}",
cell.uuid(),
neighbor_key,
neighbor_cell.uuid(),
cell_key,
back_neighbor,
),
});
}
if cell.periodic_vertex_offsets().is_some()
|| neighbor_cell.periodic_vertex_offsets().is_some()
{
continue;
}
let cell1_facet_vertices = Self::facet_vertices_in_cell_order(cell, facet_idx)?;
let cell2_facet_vertices =
Self::facet_vertices_in_cell_order(neighbor_cell, mirror_idx)?;
let (currently_coherent, observed_odd_permutation, expected_odd_permutation) =
Self::facet_permutation_parity(cell, facet_idx, neighbor_cell, mirror_idx)?;
if !currently_coherent {
return Err(TdsError::OrientationViolation {
cell1_key: cell_key,
cell1_uuid: cell.uuid(),
cell2_key: neighbor_key,
cell2_uuid: neighbor_cell.uuid(),
cell1_facet_index: facet_idx,
cell2_facet_index: mirror_idx,
facet_vertices: cell1_facet_vertices.into_iter().collect(),
cell2_facet_vertices: cell2_facet_vertices.into_iter().collect(),
observed_odd_permutation,
expected_odd_permutation,
});
}
}
}
Ok(())
}
fn facet_vertices_in_cell_order(
cell: &Cell<T, U, V, D>,
omit_idx: usize,
) -> Result<SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>, TdsValidationError> {
if omit_idx >= cell.number_of_vertices() {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Facet index {omit_idx} out of bounds for cell {:?} with {} vertices during orientation validation",
cell.uuid(),
cell.number_of_vertices(),
),
});
}
let mut facet_vertices = SmallBuffer::new();
for (idx, &vkey) in cell.vertices().iter().enumerate() {
if idx != omit_idx {
facet_vertices.push(vkey);
}
}
Ok(facet_vertices)
}
fn facet_vertex_identities_in_cell_order(
cell: &Cell<T, U, V, D>,
omit_idx: usize,
) -> Result<SmallBuffer<(VertexKey, [i16; D]), MAX_PRACTICAL_DIMENSION_SIZE>, TdsValidationError>
{
if omit_idx >= cell.number_of_vertices() {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Facet index {omit_idx} out of bounds for cell {:?} with {} vertices during orientation validation",
cell.uuid(),
cell.number_of_vertices(),
),
});
}
let periodic_offsets = cell.periodic_vertex_offsets();
if let Some(offsets) = periodic_offsets
&& offsets.len() != cell.number_of_vertices()
{
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Cell {:?} periodic offset count {} does not match vertex count {} during orientation validation",
cell.uuid(),
offsets.len(),
cell.number_of_vertices(),
),
});
}
let mut facet_identities: SmallBuffer<(VertexKey, [i16; D]), MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::new();
for (idx, &vkey) in cell.vertices().iter().enumerate() {
if idx == omit_idx {
continue;
}
let raw_offset = periodic_offsets.map_or([0_i8; D], |offsets| offsets[idx]);
let mut offset = [0_i16; D];
for axis in 0..D {
offset[axis] = i16::from(raw_offset[axis]);
}
facet_identities.push((vkey, offset));
}
let mut anchor_key = u64::MAX;
let mut anchor_offset = [0_i16; D];
for (vkey, offset) in &facet_identities {
let key_value = vkey.data().as_ffi();
if key_value < anchor_key || (key_value == anchor_key && *offset < anchor_offset) {
anchor_key = key_value;
anchor_offset = *offset;
}
}
for (_, offset) in &mut facet_identities {
for axis in 0..D {
offset[axis] -= anchor_offset[axis];
}
}
Ok(facet_identities)
}
fn facet_permutation_parity(
cell: &Cell<T, U, V, D>,
facet_idx: usize,
neighbor_cell: &Cell<T, U, V, D>,
mirror_idx: usize,
) -> Result<(bool, bool, bool), TdsValidationError> {
let cell_facet_identities = Self::facet_vertex_identities_in_cell_order(cell, facet_idx)?;
let neighbor_facet_identities =
Self::facet_vertex_identities_in_cell_order(neighbor_cell, mirror_idx)?;
let observed_odd_permutation = Self::permutation_is_odd(
&cell_facet_identities[..],
&neighbor_facet_identities[..],
)
.ok_or_else(|| TdsError::InconsistentDataStructure {
message: format!(
"Could not derive facet-order permutation parity between cells {:?} and {:?}",
cell.uuid(),
neighbor_cell.uuid(),
),
})?;
let expected_odd_permutation = (facet_idx + mirror_idx).is_multiple_of(2);
Ok((
observed_odd_permutation == expected_odd_permutation,
observed_odd_permutation,
expected_odd_permutation,
))
}
fn permutation_is_odd<Id: PartialEq>(source_order: &[Id], target_order: &[Id]) -> Option<bool> {
if source_order.len() != target_order.len() {
return None;
}
let mut target_positions: SmallBuffer<usize, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(source_order.len());
let mut used_target_indices: SmallBuffer<bool, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::from_elem(false, target_order.len());
for source_vertex in source_order {
let mut matched_target_position = None;
for (target_idx, target_vertex) in target_order.iter().enumerate() {
if target_vertex == source_vertex && !used_target_indices[target_idx] {
matched_target_position = Some(target_idx);
used_target_indices[target_idx] = true;
break;
}
}
target_positions.push(matched_target_position?);
}
if used_target_indices.iter().any(|used| !*used) {
return None;
}
let mut is_odd = false;
for i in 0..target_positions.len() {
for j in (i + 1)..target_positions.len() {
if target_positions[i] > target_positions[j] {
is_odd = !is_odd;
}
}
}
Some(is_odd)
}
fn validate_facet_sharing_with_facet_to_cells_map(
facet_to_cells: &FacetToCellsMap,
) -> Result<(), TdsValidationError> {
for (facet_key, cell_facet_pairs) in facet_to_cells {
if cell_facet_pairs.len() > 2 {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Facet with key {} is shared by {} cells, but should be shared by at most 2 cells in a valid triangulation",
facet_key,
cell_facet_pairs.len()
),
});
}
}
Ok(())
}
pub fn is_valid(&self) -> Result<(), TdsValidationError> {
self.validate_vertex_mappings()?;
self.validate_cell_mappings()?;
self.validate_cell_vertex_keys()?;
self.validate_vertex_incidence()?;
self.validate_no_duplicate_cells()?;
let facet_to_cells = self.build_facet_to_cells_map()?;
Self::validate_facet_sharing_with_facet_to_cells_map(&facet_to_cells)?;
self.validate_neighbors_with_facet_to_cells_map(&facet_to_cells)?;
self.validate_coherent_orientation()?;
Ok(())
}
pub fn validate(&self) -> Result<(), TdsValidationError>
where
T: CoordinateScalar,
{
for (_vertex_key, vertex) in &self.vertices {
if let Err(source) = (*vertex).is_valid() {
return Err(TdsError::InvalidVertex {
vertex_id: vertex.uuid(),
source,
});
}
}
for (cell_key, cell) in &self.cells {
if let Err(source) = cell.is_valid() {
let Some(cell_id) = self.cell_uuid_from_key(cell_key) else {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Cell key {cell_key:?} has no UUID mapping during validation",
),
});
};
return Err(TdsError::InvalidCell { cell_id, source });
}
}
self.is_valid()
}
pub(crate) fn validation_report(&self) -> Result<(), TriangulationValidationReport> {
let mut violations = Vec::new();
if let Err(e) = self.validate_vertex_mappings() {
violations.push(InvariantViolation {
kind: InvariantKind::VertexMappings,
error: e.into(),
});
}
if let Err(e) = self.validate_cell_mappings() {
violations.push(InvariantViolation {
kind: InvariantKind::CellMappings,
error: e.into(),
});
}
if !violations.is_empty() {
return Err(TriangulationValidationReport { violations });
}
let mut cell_vertex_keys_ok = true;
if let Err(e) = self.validate_cell_vertex_keys() {
cell_vertex_keys_ok = false;
violations.push(InvariantViolation {
kind: InvariantKind::CellVertexKeys,
error: e.into(),
});
}
if let Err(e) = self.validate_vertex_incidence() {
violations.push(InvariantViolation {
kind: InvariantKind::VertexIncidence,
error: e.into(),
});
}
if !cell_vertex_keys_ok {
return Err(TriangulationValidationReport { violations });
}
if let Err(e) = self.validate_no_duplicate_cells() {
violations.push(InvariantViolation {
kind: InvariantKind::DuplicateCells,
error: e.into(),
});
}
let mut neighbor_consistency_ok = false;
match self.build_facet_to_cells_map() {
Ok(facet_to_cells) => {
if let Err(e) =
Self::validate_facet_sharing_with_facet_to_cells_map(&facet_to_cells)
{
violations.push(InvariantViolation {
kind: InvariantKind::FacetSharing,
error: e.into(),
});
}
if let Err(e) = self.validate_neighbors_with_facet_to_cells_map(&facet_to_cells) {
violations.push(InvariantViolation {
kind: InvariantKind::NeighborConsistency,
error: e.into(),
});
} else {
neighbor_consistency_ok = true;
}
}
Err(e) => {
violations.push(InvariantViolation {
kind: InvariantKind::FacetSharing,
error: e.clone().into(),
});
violations.push(InvariantViolation {
kind: InvariantKind::NeighborConsistency,
error: e.into(),
});
}
}
if neighbor_consistency_ok && let Err(e) = self.validate_coherent_orientation() {
violations.push(InvariantViolation {
kind: InvariantKind::CoherentOrientation,
error: e.into(),
});
}
if !self.is_connected() {
violations.push(InvariantViolation {
kind: InvariantKind::Connectedness,
error: TdsValidationError::InconsistentDataStructure {
message: format!(
"Disconnected triangulation: cell neighbor graph is not a single \
connected component ({} cells total)",
self.cells.len()
),
}
.into(),
});
}
if violations.is_empty() {
Ok(())
} else {
Err(TriangulationValidationReport { violations })
}
}
fn validate_neighbors_with_facet_to_cells_map(
&self,
facet_to_cells: &FacetToCellsMap,
) -> Result<(), TdsValidationError> {
self.validate_neighbor_pointers_match_facet_to_cells_map(facet_to_cells)?;
let cell_vertices = self.build_cell_vertex_sets()?;
self.validate_neighbors_with_precomputed_vertex_sets(&cell_vertices)
}
fn validate_neighbor_pointers_match_facet_to_cells_map(
&self,
facet_to_cells: &FacetToCellsMap,
) -> Result<(), TdsValidationError> {
for (facet_key, cell_facet_pairs) in facet_to_cells {
match cell_facet_pairs.as_slice() {
[handle] => {
let cell_key = handle.cell_key();
let facet_index = handle.facet_index() as usize;
let cell = self.cells.get(cell_key).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!(
"Cell key {cell_key:?} not found during neighbor validation"
),
}
})?;
if let Some(neighbors) = cell.neighbors() {
let neighbor = neighbors.get(facet_index).and_then(|n| *n);
if let Some(neighbor_key) = neighbor {
if neighbor_key == cell_key {
if Self::allows_periodic_self_neighbor(cell) {
continue;
}
return Err(TdsError::InvalidNeighbors {
message: format!(
"Boundary facet {facet_key} has non-periodic self-neighbor across cell {}[{facet_index}]",
cell.uuid(),
),
});
}
return Err(TdsError::InvalidNeighbors {
message: format!(
"Boundary facet {facet_key} unexpectedly has a neighbor across cell {}[{facet_index}] -> {neighbor_key:?}",
cell.uuid(),
),
});
}
}
}
[a, b] => {
let first_cell_key = a.cell_key();
let first_facet_index = a.facet_index() as usize;
let second_cell_key = b.cell_key();
let second_facet_index = b.facet_index() as usize;
let first_cell = self.cells.get(first_cell_key).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!(
"Cell key {first_cell_key:?} not found during neighbor validation"
),
}
})?;
let second_cell = self.cells.get(second_cell_key).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!(
"Cell key {second_cell_key:?} not found during neighbor validation"
),
}
})?;
let first_neighbor = first_cell
.neighbors()
.and_then(|n| n.get(first_facet_index))
.and_then(|n| *n);
let second_neighbor = second_cell
.neighbors()
.and_then(|n| n.get(second_facet_index))
.and_then(|n| *n);
if first_neighbor != Some(second_cell_key)
|| second_neighbor != Some(first_cell_key)
{
return Err(TdsError::InvalidNeighbors {
message: format!(
"Interior facet {facet_key} has inconsistent neighbor pointers: {}[{first_facet_index}] -> {first_neighbor:?}, {}[{second_facet_index}] -> {second_neighbor:?}",
first_cell.uuid(),
second_cell.uuid(),
),
});
}
}
_ => {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Facet with key {facet_key} is shared by {} cells, but should be shared by at most 2 cells in a valid triangulation",
cell_facet_pairs.len()
),
});
}
}
}
Ok(())
}
fn validate_neighbors_with_precomputed_vertex_sets(
&self,
cell_vertices: &CellVerticesMap,
) -> Result<(), TdsValidationError> {
for (cell_key, cell) in &self.cells {
let Some(neighbors_buf) = &cell.neighbors else {
continue; };
let neighbors: Vec<Option<CellKey>> = neighbors_buf.iter().copied().collect();
self.validate_neighbor_topology(cell_key, &neighbors)?;
let this_vertices = cell_vertices.get(&cell_key).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!(
"Cell {} (key {cell_key:?}) missing from precomputed vertex set map during neighbor validation",
cell.uuid()
),
}
})?;
for (facet_idx, neighbor_key_opt) in neighbors.iter().enumerate() {
let Some(neighbor_key) = neighbor_key_opt else {
continue;
};
if *neighbor_key == cell_key {
if Self::allows_periodic_self_neighbor(cell) {
continue;
}
return Err(TdsError::InvalidNeighbors {
message: format!(
"Cell {:?} has non-periodic self-neighbor at facet index {facet_idx}",
cell.uuid(),
),
});
}
let Some(neighbor_cell) = self.cells.get(*neighbor_key) else {
return Err(TdsError::InvalidNeighbors {
message: format!("Neighbor cell {neighbor_key:?} not found"),
});
};
let neighbor_vertices = cell_vertices.get(neighbor_key).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!(
"Neighbor cell {} (key {neighbor_key:?}) missing from precomputed vertex set map during neighbor validation",
neighbor_cell.uuid()
),
}
})?;
Self::validate_shared_facet_count(
cell,
neighbor_cell,
this_vertices,
neighbor_vertices,
)?;
let mirror_idx = Self::compute_and_verify_mirror_facet(
cell,
facet_idx,
neighbor_cell,
this_vertices,
)?;
Self::validate_shared_facet_vertices(
cell,
facet_idx,
neighbor_cell,
mirror_idx,
this_vertices,
neighbor_vertices,
)?;
Self::validate_mutual_neighbor_back_reference(
cell_key,
cell,
facet_idx,
neighbor_cell,
mirror_idx,
)?;
}
}
Ok(())
}
fn build_cell_vertex_sets(&self) -> Result<CellVerticesMap, TdsValidationError> {
let mut cell_vertices: CellVerticesMap = fast_hash_map_with_capacity(self.cells.len());
for cell_key in self.cells.keys() {
let vertices = self.get_cell_vertices(cell_key)?;
let vertex_set: VertexKeySet = vertices.iter().copied().collect();
cell_vertices.insert(cell_key, vertex_set);
}
Ok(cell_vertices)
}
fn validate_shared_facet_count(
cell: &Cell<T, U, V, D>,
neighbor_cell: &Cell<T, U, V, D>,
this_vertices: &VertexKeySet,
neighbor_vertices: &VertexKeySet,
) -> Result<(), TdsValidationError> {
let shared_count = this_vertices.intersection(neighbor_vertices).count();
if shared_count != D {
return Err(TdsError::NotNeighbors {
cell1: cell.uuid(),
cell2: neighbor_cell.uuid(),
});
}
Ok(())
}
fn compute_and_verify_mirror_facet(
cell: &Cell<T, U, V, D>,
facet_idx: usize,
neighbor_cell: &Cell<T, U, V, D>,
this_vertices: &VertexKeySet,
) -> Result<usize, TdsValidationError> {
let mirror_idx = cell
.mirror_facet_index(facet_idx, neighbor_cell)
.ok_or_else(|| TdsError::InvalidNeighbors {
message: format!(
"Could not find mirror facet: cell {:?}[{facet_idx}] -> neighbor {:?}",
cell.uuid(),
neighbor_cell.uuid()
),
})?;
let expected_mirror_idx =
Self::compute_expected_mirror_facet_index(cell, neighbor_cell, this_vertices)?;
if mirror_idx != expected_mirror_idx {
return Err(TdsError::InvalidNeighbors {
message: format!(
"Mirror facet index mismatch: cell {:?}[{facet_idx}] -> neighbor {:?}; mirror_facet_index returned {mirror_idx} but shared-vertex analysis implies {expected_mirror_idx}",
cell.uuid(),
neighbor_cell.uuid()
),
});
}
Ok(mirror_idx)
}
fn compute_expected_mirror_facet_index(
cell: &Cell<T, U, V, D>,
neighbor_cell: &Cell<T, U, V, D>,
this_vertices: &VertexKeySet,
) -> Result<usize, TdsValidationError> {
let mut expected_mirror_idx: Option<usize> = None;
for (idx, &neighbor_vkey) in neighbor_cell.vertices().iter().enumerate() {
if !this_vertices.contains(&neighbor_vkey) {
if expected_mirror_idx.is_some() {
return Err(TdsError::InvalidNeighbors {
message: format!(
"Mirror facet is ambiguous: cell {:?} and neighbor {:?} differ by more than one vertex",
cell.uuid(),
neighbor_cell.uuid()
),
});
}
expected_mirror_idx = Some(idx);
}
}
expected_mirror_idx.ok_or_else(|| TdsError::InvalidNeighbors {
message: format!(
"Mirror facet could not be determined: cell {:?} and neighbor {:?} appear to share all vertices (duplicate cells?)",
cell.uuid(),
neighbor_cell.uuid()
),
})
}
fn validate_shared_facet_vertices(
cell: &Cell<T, U, V, D>,
facet_idx: usize,
neighbor_cell: &Cell<T, U, V, D>,
mirror_idx: usize,
this_vertices: &VertexKeySet,
neighbor_vertices: &VertexKeySet,
) -> Result<(), TdsValidationError> {
for (idx, &vkey) in cell.vertices().iter().enumerate() {
if idx == facet_idx {
continue;
}
if !neighbor_vertices.contains(&vkey) {
return Err(TdsError::InvalidNeighbors {
message: format!(
"Shared facet mismatch: cell {:?}[{facet_idx}] -> neighbor {:?}[{mirror_idx}] is missing vertex {vkey:?} from the shared facet",
cell.uuid(),
neighbor_cell.uuid(),
),
});
}
}
for (idx, &vkey) in neighbor_cell.vertices().iter().enumerate() {
if idx == mirror_idx {
continue;
}
if !this_vertices.contains(&vkey) {
return Err(TdsError::InvalidNeighbors {
message: format!(
"Shared facet mismatch: neighbor {:?}[{mirror_idx}] -> cell {:?}[{facet_idx}] is missing vertex {vkey:?} from the shared facet",
neighbor_cell.uuid(),
cell.uuid(),
),
});
}
}
Ok(())
}
fn validate_mutual_neighbor_back_reference(
cell_key: CellKey,
cell: &Cell<T, U, V, D>,
facet_idx: usize,
neighbor_cell: &Cell<T, U, V, D>,
mirror_idx: usize,
) -> Result<(), TdsValidationError> {
let Some(neighbor_neighbors) = &neighbor_cell.neighbors else {
return Err(TdsError::InvalidNeighbors {
message: format!(
"Neighbor relationship not mutual: {:?}[{facet_idx}] -> {:?} (neighbor has no neighbors)",
cell.uuid(),
neighbor_cell.uuid()
),
});
};
let back_ref = neighbor_neighbors.get(mirror_idx).copied().flatten();
if back_ref != Some(cell_key) {
return Err(TdsError::InvalidNeighbors {
message: format!(
"Neighbor relationship not mutual: {:?}[{facet_idx}] -> {:?}[{mirror_idx}] (expected back-reference)",
cell.uuid(),
neighbor_cell.uuid()
),
});
}
Ok(())
}
}
impl<T, U, V, const D: usize> PartialEq for Tds<T, U, V, D>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
fn eq(&self, other: &Self) -> bool {
if self.vertices.len() != other.vertices.len()
|| self.cells.len() != other.cells.len()
|| self.uuid_to_vertex_key.len() != other.uuid_to_vertex_key.len()
|| self.uuid_to_cell_key.len() != other.uuid_to_cell_key.len()
{
return false;
}
let mut self_vertices: Vec<_> = self.vertices.values().collect();
let mut other_vertices: Vec<_> = other.vertices.values().collect();
self_vertices.sort_by(|a, b| {
let a_coords = *a.point().coords();
let b_coords = *b.point().coords();
a_coords
.partial_cmp(&b_coords)
.unwrap_or(CmpOrdering::Equal)
});
other_vertices.sort_by(|a, b| {
let a_coords = *a.point().coords();
let b_coords = *b.point().coords();
a_coords
.partial_cmp(&b_coords)
.unwrap_or(CmpOrdering::Equal)
});
if self_vertices != other_vertices {
return false;
}
let mut self_cells: Vec<_> = self.cells.values().collect();
let mut other_cells: Vec<_> = other.cells.values().collect();
let sort_key = |cell: &Cell<T, U, V, D>, tds: &Self| -> Vec<Uuid> {
let mut ids: Vec<Uuid> = cell
.vertices()
.iter()
.filter_map(|&vkey| tds.get_vertex_by_key(vkey).map(Vertex::uuid))
.collect();
ids.sort_unstable();
ids
};
self_cells.sort_by(|a, b| {
sort_key(a, self)
.partial_cmp(&sort_key(b, self))
.unwrap_or(CmpOrdering::Equal)
});
other_cells.sort_by(|a, b| {
sort_key(a, other)
.partial_cmp(&sort_key(b, other))
.unwrap_or(CmpOrdering::Equal)
});
if self_cells.len() != other_cells.len() {
return false;
}
for (self_cell, other_cell) in self_cells.iter().zip(other_cells.iter()) {
if !self_cell.eq_by_vertices(self, other_cell, other) {
return false;
}
}
true
}
}
impl<T, U, V, const D: usize> Eq for Tds<T, U, V, D>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
}
impl<T, U, V, const D: usize> Serialize for Tds<T, U, V, D>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let cell_vertices: FastHashMap<Uuid, Vec<Uuid>> = self
.cells
.iter()
.map(|(_cell_key, cell)| {
let cell_uuid = cell.uuid();
let vertex_uuids = cell.vertex_uuids(self).map_err(serde::ser::Error::custom)?;
Ok((cell_uuid, vertex_uuids.to_vec()))
})
.collect::<Result<_, _>>()?;
let mut state = serializer.serialize_struct("Tds", 3)?;
state.serialize_field("vertices", &self.vertices)?;
state.serialize_field("cells", &self.cells)?;
state.serialize_field("cell_vertices", &cell_vertices)?;
state.end()
}
}
impl<'de, T, U, V, const D: usize> Deserialize<'de> for Tds<T, U, V, D>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
fn deserialize<D2>(deserializer: D2) -> Result<Self, D2::Error>
where
D2: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "snake_case")]
enum Field {
Vertices,
Cells,
CellVertices,
}
struct TdsVisitor<T, U, V, const D: usize>(PhantomData<(T, U, V)>);
impl<'de, T, U, V, const D: usize> Visitor<'de> for TdsVisitor<T, U, V, D>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
type Value = Tds<T, U, V, D>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct Tds")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut vertices: Option<StorageMap<VertexKey, Vertex<T, U, D>>> = None;
let mut cells: Option<StorageMap<CellKey, Cell<T, U, V, D>>> = None;
let mut cell_vertices: Option<FastHashMap<Uuid, Vec<Uuid>>> = None;
while let Some(key) = map.next_key()? {
match key {
Field::Vertices => {
if vertices.is_some() {
return Err(de::Error::duplicate_field("vertices"));
}
vertices = Some(map.next_value()?);
}
Field::Cells => {
if cells.is_some() {
return Err(de::Error::duplicate_field("cells"));
}
cells = Some(map.next_value()?);
}
Field::CellVertices => {
if cell_vertices.is_some() {
return Err(de::Error::duplicate_field("cell_vertices"));
}
cell_vertices = Some(map.next_value()?);
}
}
}
let vertices = vertices.ok_or_else(|| de::Error::missing_field("vertices"))?;
let mut cells = cells.ok_or_else(|| de::Error::missing_field("cells"))?;
let cell_vertices =
cell_vertices.ok_or_else(|| de::Error::missing_field("cell_vertices"))?;
let mut uuid_to_vertex_key = fast_hash_map_with_capacity(vertices.len());
for (vertex_key, vertex) in &vertices {
uuid_to_vertex_key.insert(vertex.uuid(), vertex_key);
}
let mut uuid_to_cell_key = fast_hash_map_with_capacity(cells.len());
for (cell_key, cell) in &cells {
uuid_to_cell_key.insert(cell.uuid(), cell_key);
}
for (_cell_key, cell) in &mut cells {
let cell_uuid = cell.uuid();
if let Some(vertex_uuids) = cell_vertices.get(&cell_uuid) {
cell.clear_vertex_keys();
for &vertex_uuid in vertex_uuids {
if let Some(&vertex_key) = uuid_to_vertex_key.get(&vertex_uuid) {
cell.push_vertex_key(vertex_key);
} else {
return Err(de::Error::custom(format!(
"Vertex UUID {vertex_uuid} referenced by cell {cell_uuid} not found in vertices"
)));
}
}
} else {
return Err(de::Error::custom(format!(
"No vertex UUIDs found for cell {cell_uuid}"
)));
}
}
let mut tds = Tds {
vertices,
cells,
uuid_to_vertex_key,
uuid_to_cell_key,
construction_state: TriangulationConstructionState::Constructed,
generation: Arc::new(AtomicU64::new(0)),
};
tds.assign_neighbors().map_err(de::Error::custom)?;
tds.assign_incident_cells().map_err(de::Error::custom)?;
Ok(tds)
}
}
const FIELDS: &[&str] = &["vertices", "cells", "cell_vertices"];
deserializer.deserialize_struct("Tds", FIELDS, TdsVisitor(PhantomData))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::algorithms::incremental_insertion::InsertionError;
use crate::core::{delaunay_triangulation::DelaunayTriangulation, vertex::VertexBuilder};
use crate::geometry::point::Point;
use crate::geometry::traits::coordinate::Coordinate;
use crate::vertex;
use slotmap::KeyData;
#[cfg(test)]
fn create_vertex_with_uuid<T, U, const D: usize>(
point: Point<T, D>,
uuid: Uuid,
data: Option<U>,
) -> Vertex<T, U, D>
where
T: CoordinateScalar,
U: DataType,
{
let mut vertex = data.map_or_else(
|| {
VertexBuilder::default()
.point(point)
.build()
.expect("Failed to build vertex")
},
|data_value| {
VertexBuilder::default()
.point(point)
.data(data_value)
.build()
.expect("Failed to build vertex")
},
);
vertex.set_uuid(uuid).expect("Failed to set UUID");
vertex
}
fn initial_simplex_vertices_3d() -> [Vertex<f64, (), 3>; 4] {
[
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]),
]
}
#[test]
fn test_facet_key_for_cell_facet_maps_periodic_derivation_errors() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let cell_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
tds.get_cell_by_key_mut(cell_key)
.unwrap()
.set_periodic_vertex_offsets(vec![[-128_i8, 0_i8], [127_i8, 0_i8], [0_i8, 0_i8]]);
let err = tds.facet_key_for_cell_facet(cell_key, 2).unwrap_err();
assert!(matches!(
err,
TdsValidationError::InconsistentDataStructure { message }
if message.contains("Failed to derive periodic facet key")
&& message.contains("facet 2")
));
}
#[test]
fn test_facet_vertex_identities_anchor_uses_lexicographic_key_offset() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let cell_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
{
let cell = tds.get_cell_by_key_mut(cell_key).unwrap();
cell.push_vertex_key(v_a);
cell.set_periodic_vertex_offsets(vec![[5, 0], [9, 0], [8, 0], [1, 0]]);
}
let cell = tds.get_cell(cell_key).unwrap();
let identities =
Tds::<f64, (), (), 2>::facet_vertex_identities_in_cell_order(cell, 2).unwrap();
assert_eq!(identities.len(), 3);
let mut offsets_for_a: Vec<[i16; 2]> = identities
.iter()
.filter_map(|(vkey, offset)| (*vkey == v_a).then_some(*offset))
.collect();
offsets_for_a.sort_unstable();
assert_eq!(offsets_for_a, vec![[0, 0], [4, 0]]);
let b_offset = identities
.iter()
.find_map(|(vkey, offset)| (*vkey == v_b).then_some(*offset))
.expect("identity list should include v_b");
assert_eq!(b_offset, [8, 0]);
}
#[test]
fn test_facet_permutation_parity_derives_coherent_and_incoherent_cases() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell: Cell<f64, (), (), 2> = Cell::new(vec![v_a, v_b, v_c], None).unwrap();
let coherent_neighbor: Cell<f64, (), (), 2> = Cell::new(vec![v_b, v_a, v_d], None).unwrap();
let coherent_mirror_idx = cell.mirror_facet_index(2, &coherent_neighbor).unwrap();
let (currently_coherent, observed_odd_permutation, expected_odd_permutation) =
Tds::facet_permutation_parity(&cell, 2, &coherent_neighbor, coherent_mirror_idx)
.unwrap();
assert!(currently_coherent);
assert!(observed_odd_permutation);
assert!(expected_odd_permutation);
let incoherent_neighbor: Cell<f64, (), (), 2> =
Cell::new(vec![v_a, v_b, v_d], None).unwrap();
let incoherent_mirror_idx = cell.mirror_facet_index(2, &incoherent_neighbor).unwrap();
let (currently_coherent, observed_odd_permutation, expected_odd_permutation) =
Tds::facet_permutation_parity(&cell, 2, &incoherent_neighbor, incoherent_mirror_idx)
.unwrap();
assert!(!currently_coherent);
assert!(!observed_odd_permutation);
assert!(expected_odd_permutation);
}
#[test]
fn test_facet_permutation_parity_smoke_4d() {
let mut tds: Tds<f64, (), (), 4> = Tds::empty();
let v_a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0]))
.unwrap();
let v_b = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0, 0.0]))
.unwrap();
let v_c = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0, 0.0]))
.unwrap();
let v_d = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0, 0.0]))
.unwrap();
let v_e = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 1.0]))
.unwrap();
let v_f = tds
.insert_vertex_with_mapping(vertex!([1.0, 1.0, 1.0, 1.0]))
.unwrap();
let cell: Cell<f64, (), (), 4> = Cell::new(vec![v_a, v_b, v_c, v_d, v_e], None).unwrap();
let coherent_neighbor: Cell<f64, (), (), 4> =
Cell::new(vec![v_b, v_a, v_c, v_d, v_f], None).unwrap();
let coherent_mirror_idx = cell.mirror_facet_index(4, &coherent_neighbor).unwrap();
let (currently_coherent, observed_odd_permutation, expected_odd_permutation) =
Tds::facet_permutation_parity(&cell, 4, &coherent_neighbor, coherent_mirror_idx)
.unwrap();
assert!(currently_coherent);
assert!(observed_odd_permutation);
assert!(expected_odd_permutation);
let incoherent_neighbor: Cell<f64, (), (), 4> =
Cell::new(vec![v_a, v_b, v_c, v_d, v_f], None).unwrap();
let incoherent_mirror_idx = cell.mirror_facet_index(4, &incoherent_neighbor).unwrap();
let (currently_coherent, observed_odd_permutation, expected_odd_permutation) =
Tds::facet_permutation_parity(&cell, 4, &incoherent_neighbor, incoherent_mirror_idx)
.unwrap();
assert!(!currently_coherent);
assert!(!observed_odd_permutation);
assert!(expected_odd_permutation);
}
#[test]
fn test_repair_degenerate_cells() {
use crate::core::cell::Cell;
let vertices = [
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 1.0]),
];
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_keys: Vec<_> = vertices
.iter()
.copied()
.map(|v| tds.insert_vertex_with_mapping(v).unwrap())
.collect();
let good_cell = Cell::new(vec![v_keys[0], v_keys[1], v_keys[2]], None).unwrap();
let good_cell_key = tds.insert_cell_with_mapping(good_cell).unwrap();
let bad_cell = Cell::new(vec![v_keys[0], v_keys[1], v_keys[3]], None).unwrap();
let bad_cell_key = tds.insert_cell_with_mapping(bad_cell).unwrap();
let removed_target_cell = Cell::new(vec![v_keys[1], v_keys[2], v_keys[3]], None).unwrap();
let removed_target_key = tds.insert_cell_with_mapping(removed_target_cell).unwrap();
assert!(tds.remove_cell_by_key(removed_target_key).is_some());
{
let bad_cell_mut = tds.cells_mut().get_mut(bad_cell_key).unwrap();
let mut neighbors = crate::core::collections::NeighborBuffer::new();
neighbors.push(Some(removed_target_key));
neighbors.push(None);
neighbors.push(None);
bad_cell_mut.neighbors = Some(neighbors);
}
let removed_count = tds.repair_degenerate_cells();
assert_eq!(
removed_count, 1,
"Expected exactly 1 degenerate cell removed (bad_cell with dangling neighbor), got {removed_count}",
);
assert_eq!(tds.number_of_cells(), 1);
assert!(tds.cells.contains_key(good_cell_key));
assert!(!tds.cells.contains_key(bad_cell_key));
assert_eq!(tds.repair_degenerate_cells(), 0);
assert!(tds.is_valid().is_ok());
println!("✓ repair_degenerate_cells removes cells with dangling neighbor pointers");
}
#[test]
fn test_add_vertex_basic_insertion_succeeds() {
let initial_vertices = initial_simplex_vertices_3d();
let mut dt = DelaunayTriangulation::new(&initial_vertices).unwrap();
let vertex = vertex!([1.0, 2.0, 3.0]);
let result = dt.insert(vertex);
assert!(result.is_ok(), "Basic vertex addition should succeed");
assert_eq!(dt.number_of_vertices(), 5);
}
#[test]
fn test_add_vertex_duplicate_coordinates_rejected() {
let initial_vertices = initial_simplex_vertices_3d();
let mut dt = DelaunayTriangulation::new(&initial_vertices).unwrap();
let vertex = vertex!([1.0, 2.0, 3.0]);
let duplicate = vertex!([1.0, 2.0, 3.0]);
dt.insert(vertex).unwrap();
let result = dt.insert(duplicate);
assert!(
matches!(result, Err(InsertionError::DuplicateCoordinates { .. })),
"insert() should reject duplicate coordinates created via vertex! (before UUID), got: {result:?}"
);
}
#[test]
fn test_add_vertex_duplicate_uuid_rejected() {
let initial_vertices = initial_simplex_vertices_3d();
let mut dt = DelaunayTriangulation::new(&initial_vertices).unwrap();
let vertex1 = vertex!([1.0, 2.0, 3.0]);
let uuid1 = vertex1.uuid();
dt.insert(vertex1).unwrap();
let vertex2 = create_vertex_with_uuid(Point::new([4.0, 5.0, 6.0]), uuid1, None);
let result = dt.insert(vertex2);
assert!(
matches!(
result,
Err(InsertionError::Construction(
crate::core::triangulation::TriangulationConstructionError::Tds(
TdsConstructionError::DuplicateUuid {
entity: EntityKind::Vertex,
..
},
),
))
),
"Same UUID with different coordinates should fail with DuplicateUuid"
);
}
#[test]
fn test_add_vertex_increases_counts_and_leaves_tds_valid() {
let initial_vertices = initial_simplex_vertices_3d();
let mut dt = DelaunayTriangulation::new(&initial_vertices).unwrap();
let initial_cell_count = dt.number_of_cells();
let new_vertex = vertex!([0.5, 0.5, 0.5]);
dt.insert(new_vertex).unwrap();
assert_eq!(dt.number_of_vertices(), 5);
assert!(
dt.number_of_cells() >= initial_cell_count,
"Cell count should not decrease"
);
assert!(
dt.as_triangulation().tds.is_valid().is_ok(),
"TDS should remain valid"
);
}
#[test]
fn test_add_vertex_is_accessible_by_uuid_and_coordinates() {
let initial_vertices = initial_simplex_vertices_3d();
let mut dt = DelaunayTriangulation::new(&initial_vertices).unwrap();
let vertex = vertex!([1.0, 2.0, 3.0]);
let uuid = vertex.uuid();
dt.insert(vertex).unwrap();
let vertex_key = dt.as_triangulation().tds.vertex_key_from_uuid(&uuid);
assert!(
vertex_key.is_some(),
"Added vertex should be findable by UUID"
);
let stored_vertex = dt
.as_triangulation()
.tds
.get_vertex_by_key(vertex_key.unwrap())
.unwrap();
let coords = *stored_vertex.point().coords();
let expected = [1.0, 2.0, 3.0];
assert!(
coords
.iter()
.zip(expected.iter())
.all(|(a, b)| (a - b).abs() < 1e-10),
"Stored coordinates should match: got {coords:?}, expected {expected:?}"
);
}
#[test]
fn test_remove_vertex_maintains_topology_consistency() {
let vertices = [
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.5, 1.0]),
vertex!([1.5, 1.0]),
];
let mut dt = DelaunayTriangulation::new(&vertices).unwrap();
let initial_vertices = dt.number_of_vertices();
let initial_cells = dt.number_of_cells();
assert_eq!(initial_vertices, 4);
assert!(initial_cells > 0);
let vertex_to_remove = *dt.vertices().next().unwrap().1;
let vertex_uuid = vertex_to_remove.uuid();
let cells_removed = dt.remove_vertex(&vertex_to_remove).unwrap();
assert!(
dt.as_triangulation()
.tds
.vertex_key_from_uuid(&vertex_uuid)
.is_none(),
"Vertex should be removed from TDS"
);
assert!(
cells_removed > 0,
"At least one cell should have been removed"
);
assert_eq!(
dt.number_of_vertices(),
initial_vertices - 1,
"Vertex count should decrease by 1"
);
assert!(
dt.number_of_cells() < initial_cells,
"Cell count should decrease"
);
for (cell_key, cell) in dt.cells() {
if let Some(neighbors) = cell.neighbors() {
for (i, neighbor_opt) in neighbors.iter().enumerate() {
if let Some(neighbor_key) = neighbor_opt {
assert!(
dt.as_triangulation().tds.cells.contains_key(*neighbor_key),
"Cell {cell_key:?} has dangling neighbor reference at index {i}: {neighbor_key:?}"
);
}
}
}
}
assert!(
dt.as_triangulation().tds.is_valid().is_ok(),
"TDS should be valid after removing vertex"
);
println!("✓ remove_vertex maintains topology consistency");
}
#[test]
fn test_remove_vertex_nonexistent() {
let vertices = [
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let mut dt = DelaunayTriangulation::new(&vertices).unwrap();
let nonexistent_vertex = vertex!([5.0, 5.0]);
let initial_vertices = dt.number_of_vertices();
let initial_cells = dt.number_of_cells();
let cells_removed = dt.remove_vertex(&nonexistent_vertex).unwrap();
assert_eq!(cells_removed, 0, "No cells should be removed");
assert_eq!(
dt.number_of_vertices(),
initial_vertices,
"Vertex count should not change"
);
assert_eq!(
dt.number_of_cells(),
initial_cells,
"Cell count should not change"
);
println!("✓ remove_vertex handles nonexistent vertex correctly");
}
#[test]
fn test_remove_vertex_multiple_dimensions() {
{
let vertices_2d = [
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 1.0]),
];
let mut dt_2d: DelaunayTriangulation<_, (), (), 2> =
DelaunayTriangulation::new(&vertices_2d).unwrap();
let vertex = *dt_2d.vertices().next().unwrap().1;
let cells_removed = dt_2d.remove_vertex(&vertex).unwrap();
assert!(cells_removed > 0);
assert!(dt_2d.as_triangulation().tds.is_valid().is_ok());
}
{
let vertices_3d = [
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 mut dt_3d: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices_3d).unwrap();
let vertex = *dt_3d.vertices().next().unwrap().1;
let cells_removed = dt_3d.remove_vertex(&vertex).unwrap();
assert!(cells_removed > 0);
assert!(dt_3d.as_triangulation().tds.is_valid().is_ok());
}
{
let vertices_4d = [
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]),
vertex!([1.0, 1.0, 1.0, 1.0]),
];
let mut dt_4d: DelaunayTriangulation<_, (), (), 4> =
DelaunayTriangulation::new(&vertices_4d).unwrap();
let vertex = *dt_4d.vertices().next().unwrap().1;
let cells_removed = dt_4d.remove_vertex(&vertex).unwrap();
assert!(cells_removed > 0);
assert!(dt_4d.as_triangulation().tds.is_valid().is_ok());
}
println!("✓ remove_vertex works correctly in multiple dimensions");
}
#[test]
fn test_remove_vertex_no_dangling_references() {
let vertices = [
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
vertex!([0.2, 0.2, 0.2]),
];
let mut dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
let interior_coords = [0.2, 0.2, 0.2];
let (removed_vertex_key, removed_vertex_uuid) = dt
.vertices()
.find(|(_, v)| {
v.point()
.coords()
.as_slice()
.iter()
.zip(&interior_coords)
.all(|(a, b)| (a - b).abs() < 1e-10)
})
.map(|(k, v)| (k, v.uuid()))
.expect("Interior vertex should exist");
let vertex_to_remove = *dt
.vertices()
.find(|(k, _)| *k == removed_vertex_key)
.unwrap()
.1;
let cells_removed = dt.remove_vertex(&vertex_to_remove).unwrap();
assert!(cells_removed > 0, "Should have removed at least one cell");
for (cell_key, cell) in dt.cells() {
for &vk in cell.vertices() {
assert_ne!(
vk, removed_vertex_key,
"Cell {cell_key:?} still references deleted vertex {removed_vertex_key:?}"
);
}
}
assert!(
dt.as_triangulation()
.tds
.vertex_key_from_uuid(&removed_vertex_uuid)
.is_none(),
"Deleted vertex UUID should not be in mapping"
);
assert!(
dt.as_triangulation()
.tds
.get_vertex_by_key(removed_vertex_key)
.is_none(),
"Deleted vertex key should not exist in storage"
);
for (vertex_key, vertex) in dt.vertices() {
if let Some(incident_cell_key) = vertex.incident_cell {
assert!(
dt.as_triangulation()
.tds
.cells
.contains_key(incident_cell_key),
"Vertex {vertex_key:?} has dangling incident_cell pointer to {incident_cell_key:?}"
);
let incident_cell = dt
.as_triangulation()
.tds
.get_cell(incident_cell_key)
.unwrap();
assert!(
incident_cell.contains_vertex(vertex_key),
"Vertex {vertex_key:?} incident_cell {incident_cell_key:?} does not contain the vertex"
);
}
}
assert!(
dt.as_triangulation().tds.is_valid().is_ok(),
"TDS should be valid after vertex removal"
);
println!("✓ remove_vertex leaves no dangling references to deleted vertex");
}
#[test]
fn test_find_cells_containing_vertex() {
let vertices = [
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
vertex!([0.5, 0.5, 0.5]), ];
let dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
let first_cell_key = dt.cells().next().unwrap().0;
let some_vertex_key = dt
.as_triangulation()
.tds
.get_cell_vertices(first_cell_key)
.unwrap()[0];
let cells_with_vertex = dt
.as_triangulation()
.tds
.find_cells_containing_vertex(some_vertex_key);
assert!(
!cells_with_vertex.is_empty(),
"Vertex should be in at least one cell"
);
for &cell_key in &cells_with_vertex {
let cell = dt.as_triangulation().tds.get_cell(cell_key).unwrap();
assert!(
cell.contains_vertex(some_vertex_key),
"Cell {cell_key:?} should contain vertex {some_vertex_key:?}"
);
}
let expected_count = dt
.cells()
.filter(|(_, cell)| cell.contains_vertex(some_vertex_key))
.count();
assert_eq!(
cells_with_vertex.len(),
expected_count,
"Should find all cells containing the vertex"
);
let another_vertex_key = dt.vertices().nth(1).map(|(k, _)| k).unwrap();
let cells_with_another = dt
.as_triangulation()
.tds
.find_cells_containing_vertex(another_vertex_key);
assert!(
!cells_with_another.is_empty(),
"Another vertex should also be in at least one cell"
);
println!(
"✓ find_cells_containing_vertex correctly identifies all cells containing a vertex"
);
}
#[test]
fn test_assign_neighbors_errors_on_missing_vertex_key() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let cell_key = tds
.insert_cell_with_mapping(Cell::new(vec![a, b, c], None).unwrap())
.unwrap();
let invalid_vkey = VertexKey::from(KeyData::from_ffi(u64::MAX));
tds.get_cell_by_key_mut(cell_key)
.unwrap()
.push_vertex_key(invalid_vkey);
let err = tds.assign_neighbors().unwrap_err();
assert!(matches!(err, TdsError::VertexKeyRetrievalFailed { .. }));
}
#[test]
fn test_assign_neighbors_errors_on_non_manifold_facet_sharing() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds
.insert_vertex_with_mapping(vertex!([0.0, -1.0]))
.unwrap();
let v_e = tds.insert_vertex_with_mapping(vertex!([2.0, 0.0])).unwrap();
tds.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
tds.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
tds.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_e], None).unwrap())
.unwrap();
let err = tds.assign_neighbors().unwrap_err();
assert!(matches!(err, TdsError::InconsistentDataStructure { .. }));
}
#[test]
fn test_remove_cells_by_keys_empty_is_noop() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let gen_before = tds.generation();
assert_eq!(tds.remove_cells_by_keys(&[]), 0);
assert_eq!(tds.generation(), gen_before);
}
#[test]
fn test_remove_cells_by_keys_clears_neighbor_pointers() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1 = tds
.insert_cell_with_mapping(Cell::new(vec![a, b, c], None).unwrap())
.unwrap();
let cell2 = tds
.insert_cell_with_mapping(Cell::new(vec![b, d, c], None).unwrap())
.unwrap();
tds.assign_neighbors().unwrap();
assert!(
tds.get_cell(cell1)
.unwrap()
.neighbors()
.is_some_and(|n| n.iter().any(Option::is_some))
);
let gen_before = tds.generation();
assert_eq!(tds.remove_cells_by_keys(&[cell2]), 1);
assert_eq!(tds.generation(), gen_before + 1);
for (_, cell) in tds.cells() {
if let Some(neighbors) = cell.neighbors() {
for neighbor_opt in neighbors {
assert_ne!(*neighbor_opt, Some(cell2));
}
}
}
}
#[test]
fn test_remove_cells_by_keys_repairs_incident_cells_for_affected_vertices() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1 = tds
.insert_cell_with_mapping(Cell::new(vec![a, b, c], None).unwrap())
.unwrap();
let cell2 = tds
.insert_cell_with_mapping(Cell::new(vec![b, d, c], None).unwrap())
.unwrap();
tds.assign_neighbors().unwrap();
tds.get_vertex_by_key_mut(a).unwrap().incident_cell = Some(cell1);
tds.get_vertex_by_key_mut(b).unwrap().incident_cell = Some(cell1);
tds.get_vertex_by_key_mut(c).unwrap().incident_cell = Some(cell1);
tds.get_vertex_by_key_mut(d).unwrap().incident_cell = Some(cell2);
assert_eq!(tds.remove_cells_by_keys(&[cell1]), 1);
assert!(tds.get_vertex_by_key(a).unwrap().incident_cell.is_none());
for vk in [b, c, d] {
let incident = tds
.get_vertex_by_key(vk)
.unwrap()
.incident_cell
.expect("vertex should have an incident cell after repair");
assert!(tds.cells.contains_key(incident));
let cell = tds.get_cell(incident).unwrap();
assert!(cell.contains_vertex(vk));
}
assert!(tds.is_valid().is_ok());
let cell2_ref = tds.get_cell(cell2).unwrap();
if let Some(neighbors) = cell2_ref.neighbors() {
assert!(neighbors.iter().all(|n| *n != Some(cell1)));
}
}
#[test]
fn test_tds_remove_vertex_returns_zero_when_vertex_not_in_mapping() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v = vertex!([0.0, 0.0]);
assert_eq!(tds.remove_vertex(&v).unwrap(), 0);
}
#[test]
fn test_tds_remove_vertex_repairs_neighbors_and_incident_cells_incrementally() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let origin_key = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let east_key = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let north_key = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let diagonal_key = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1 = tds
.insert_cell_with_mapping(
Cell::new(vec![origin_key, east_key, north_key], None).unwrap(),
)
.unwrap();
let cell2 = tds
.insert_cell_with_mapping(
Cell::new(vec![east_key, diagonal_key, north_key], None).unwrap(),
)
.unwrap();
tds.assign_neighbors().unwrap();
tds.get_vertex_by_key_mut(origin_key).unwrap().incident_cell = Some(cell1);
tds.get_vertex_by_key_mut(east_key).unwrap().incident_cell = Some(cell1);
tds.get_vertex_by_key_mut(north_key).unwrap().incident_cell = Some(cell1);
tds.get_vertex_by_key_mut(diagonal_key)
.unwrap()
.incident_cell = Some(cell2);
let vertex_to_remove = *tds.get_vertex_by_key(origin_key).unwrap();
let removed = tds.remove_vertex(&vertex_to_remove).unwrap();
assert_eq!(removed, 1);
assert!(tds.vertex_key_from_uuid(&vertex_to_remove.uuid()).is_none());
assert!(tds.get_vertex_by_key(origin_key).is_none());
assert!(tds.cells.contains_key(cell2));
let cell2_ref = tds.get_cell(cell2).unwrap();
if let Some(neighbors) = cell2_ref.neighbors() {
assert!(neighbors.iter().all(|n| *n != Some(cell1)));
}
for vertex_key in [east_key, north_key, diagonal_key] {
let v = tds.get_vertex_by_key(vertex_key).unwrap();
let Some(incident) = v.incident_cell else {
panic!("vertex {vertex_key:?} should have an incident cell after removal");
};
assert!(tds.cells.contains_key(incident));
assert!(tds.get_cell(incident).unwrap().contains_vertex(vertex_key));
}
}
#[test]
fn test_find_neighbors_by_key_returns_none_buffer_for_missing_cell() {
let tds: Tds<f64, (), (), 2> = Tds::empty();
let missing = CellKey::from(KeyData::from_ffi(u64::MAX));
let neighbors = tds.find_neighbors_by_key(missing);
assert_eq!(neighbors.len(), 3);
assert!(neighbors.iter().all(Option::is_none));
}
#[test]
fn test_set_neighbors_by_key_rejects_non_neighbor_and_wrong_slot() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let v_e = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap();
let cell1 = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell_far = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_d, v_e], None).unwrap())
.unwrap();
let err = tds
.set_neighbors_by_key(cell1, &[Some(cell_far), None, None])
.unwrap_err()
.0;
assert!(matches!(err, TdsError::InvalidNeighbors { .. }));
let cell2 = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
let err = tds
.set_neighbors_by_key(cell1, &[Some(cell2), None, None])
.unwrap_err()
.0;
assert!(matches!(err, TdsError::InvalidNeighbors { .. }));
}
#[test]
fn test_build_cell_vertex_sets_success() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1 = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2 = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_c, v_d], None).unwrap())
.unwrap();
let map = tds.build_cell_vertex_sets().unwrap();
assert_eq!(map.len(), 2);
let expected1: VertexKeySet = [v_a, v_b, v_c].into_iter().collect();
let expected2: VertexKeySet = [v_a, v_c, v_d].into_iter().collect();
assert_eq!(map.get(&cell1), Some(&expected1));
assert_eq!(map.get(&cell2), Some(&expected2));
}
#[test]
fn test_build_cell_vertex_sets_errors_on_missing_vertex_key() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let cell = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let invalid_vkey = VertexKey::from(KeyData::from_ffi(u64::MAX));
tds.get_cell_by_key_mut(cell)
.unwrap()
.push_vertex_key(invalid_vkey);
let err = tds.build_cell_vertex_sets().unwrap_err();
assert!(matches!(
err,
TdsError::InconsistentDataStructure { message }
if message.contains("references non-existent vertex key")
));
}
#[test]
fn test_validate_shared_facet_count_ok_for_true_neighbors() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
let cell1 = tds.get_cell(cell1_key).unwrap();
let cell2 = tds.get_cell(cell2_key).unwrap();
let this_vertices: VertexKeySet = [v_a, v_b, v_c].into_iter().collect();
let neighbor_vertices: VertexKeySet = [v_a, v_b, v_d].into_iter().collect();
assert!(
Tds::validate_shared_facet_count(cell1, cell2, &this_vertices, &neighbor_vertices)
.is_ok()
);
}
#[test]
fn test_validate_shared_facet_count_errors_for_non_neighbors() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let v_e = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell_far_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_d, v_e], None).unwrap())
.unwrap();
let cell1 = tds.get_cell(cell1_key).unwrap();
let cell_far = tds.get_cell(cell_far_key).unwrap();
let this_vertices: VertexKeySet = [v_a, v_b, v_c].into_iter().collect();
let far_vertices: VertexKeySet = [v_a, v_d, v_e].into_iter().collect();
let cell1_uuid = cell1.uuid();
let cell_far_uuid = cell_far.uuid();
let err = Tds::validate_shared_facet_count(cell1, cell_far, &this_vertices, &far_vertices)
.unwrap_err();
assert!(matches!(
err,
TdsError::NotNeighbors { cell1: c1, cell2: c2 }
if c1 == cell1_uuid && c2 == cell_far_uuid
));
}
#[test]
fn test_compute_expected_mirror_facet_index_returns_unique_vertex_index() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_d, v_a, v_b], None).unwrap())
.unwrap();
let cell1 = tds.get_cell(cell1_key).unwrap();
let cell2 = tds.get_cell(cell2_key).unwrap();
let this_vertices: VertexKeySet = [v_a, v_b, v_c].into_iter().collect();
let idx = Tds::compute_expected_mirror_facet_index(cell1, cell2, &this_vertices).unwrap();
assert_eq!(idx, 0);
}
#[test]
fn test_compute_expected_mirror_facet_index_errors_when_ambiguous() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let v_e = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_d, v_e], None).unwrap())
.unwrap();
let cell1 = tds.get_cell(cell1_key).unwrap();
let cell2 = tds.get_cell(cell2_key).unwrap();
let this_vertices: VertexKeySet = [v_a, v_b, v_c].into_iter().collect();
let err =
Tds::compute_expected_mirror_facet_index(cell1, cell2, &this_vertices).unwrap_err();
assert!(matches!(
err,
TdsError::InvalidNeighbors { message } if message.contains("ambiguous")
));
}
#[test]
fn test_compute_expected_mirror_facet_index_errors_when_duplicate_cells() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell1 = tds.get_cell(cell1_key).unwrap();
let cell2 = tds.get_cell(cell2_key).unwrap();
let this_vertices: VertexKeySet = [v_a, v_b, v_c].into_iter().collect();
let err =
Tds::compute_expected_mirror_facet_index(cell1, cell2, &this_vertices).unwrap_err();
assert!(matches!(
err,
TdsError::InvalidNeighbors { message } if message.contains("share all vertices")
));
}
#[test]
fn test_compute_and_verify_mirror_facet_ok() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
let cell1 = tds.get_cell(cell1_key).unwrap();
let cell2 = tds.get_cell(cell2_key).unwrap();
let this_vertices: VertexKeySet = [v_a, v_b, v_c].into_iter().collect();
let mirror_idx =
Tds::compute_and_verify_mirror_facet(cell1, 2, cell2, &this_vertices).unwrap();
assert_eq!(mirror_idx, 2);
}
#[test]
fn test_compute_and_verify_mirror_facet_errors_when_no_shared_facet() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
let cell1 = tds.get_cell(cell1_key).unwrap();
let cell2 = tds.get_cell(cell2_key).unwrap();
let this_vertices: VertexKeySet = [v_a, v_b, v_c].into_iter().collect();
let err =
Tds::compute_and_verify_mirror_facet(cell1, 0, cell2, &this_vertices).unwrap_err();
assert!(matches!(
err,
TdsError::InvalidNeighbors { message } if message.contains("Could not find mirror facet")
));
}
#[test]
fn test_compute_and_verify_mirror_facet_errors_on_mismatch_with_cross_check() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
let cell1 = tds.get_cell(cell1_key).unwrap();
let cell2 = tds.get_cell(cell2_key).unwrap();
let this_vertices_wrong: VertexKeySet = [v_a, v_c, v_d].into_iter().collect();
let err = Tds::compute_and_verify_mirror_facet(cell1, 2, cell2, &this_vertices_wrong)
.unwrap_err();
assert!(matches!(
err,
TdsError::InvalidNeighbors { message } if message.contains("index mismatch")
));
}
#[test]
fn test_validate_neighbors_errors_on_mirror_facet_index_mismatch() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let origin_key = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let east_key = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let north_key = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let diagonal_key = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(
Cell::new(vec![origin_key, east_key, north_key], None).unwrap(),
)
.unwrap();
let _cell2_key = tds
.insert_cell_with_mapping(
Cell::new(vec![origin_key, east_key, diagonal_key], None).unwrap(),
)
.unwrap();
tds.assign_neighbors().unwrap();
let mut cell_vertices = tds.build_cell_vertex_sets().unwrap();
let corrupted_cell1_vertices: VertexKeySet =
[origin_key, north_key, diagonal_key].into_iter().collect();
cell_vertices.insert(cell1_key, corrupted_cell1_vertices);
let err = tds
.validate_neighbors_with_precomputed_vertex_sets(&cell_vertices)
.unwrap_err();
assert!(matches!(
err,
TdsError::InvalidNeighbors { message } if message.contains("index mismatch")
));
}
#[test]
fn test_validate_shared_facet_vertices_ok() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
let cell1 = tds.get_cell(cell1_key).unwrap();
let cell2 = tds.get_cell(cell2_key).unwrap();
let this_vertices: VertexKeySet = [v_a, v_b, v_c].into_iter().collect();
let neighbor_vertices: VertexKeySet = [v_a, v_b, v_d].into_iter().collect();
assert!(
Tds::validate_shared_facet_vertices(
cell1,
2, cell2,
2, &this_vertices,
&neighbor_vertices,
)
.is_ok()
);
}
#[test]
fn test_validate_shared_facet_vertices_errors_when_mirror_index_wrong() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
let cell1 = tds.get_cell(cell1_key).unwrap();
let cell2 = tds.get_cell(cell2_key).unwrap();
let this_vertices: VertexKeySet = [v_a, v_b, v_c].into_iter().collect();
let neighbor_vertices: VertexKeySet = [v_a, v_b, v_d].into_iter().collect();
let err = Tds::validate_shared_facet_vertices(
cell1,
2, cell2,
0, &this_vertices,
&neighbor_vertices,
)
.unwrap_err();
assert!(matches!(
err,
TdsError::InvalidNeighbors { message } if message.contains("Shared facet mismatch")
));
}
#[test]
fn test_validate_mutual_neighbor_back_reference_ok() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
tds.assign_neighbors().unwrap();
let cell1 = tds.get_cell(cell1_key).unwrap();
let cell2 = tds.get_cell(cell2_key).unwrap();
assert!(
Tds::validate_mutual_neighbor_back_reference(
cell1_key, cell1, 2, cell2, 2, )
.is_ok()
);
}
#[test]
fn test_validate_mutual_neighbor_back_reference_errors_when_neighbor_has_no_neighbors() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
let cell1 = tds.get_cell(cell1_key).unwrap();
let cell2 = tds.get_cell(cell2_key).unwrap();
let err = Tds::validate_mutual_neighbor_back_reference(cell1_key, cell1, 2, cell2, 2)
.unwrap_err();
assert!(matches!(
err,
TdsError::InvalidNeighbors { message } if message.contains("neighbor has no neighbors")
));
}
#[test]
fn test_validate_mutual_neighbor_back_reference_errors_when_back_reference_missing() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
tds.assign_neighbors().unwrap();
{
let cell2_mut = tds.get_cell_by_key_mut(cell2_key).unwrap();
let neighbors = cell2_mut
.neighbors
.as_mut()
.expect("cell2 should have neighbors after assign_neighbors()");
neighbors[2] = None;
}
let cell1 = tds.get_cell(cell1_key).unwrap();
let cell2 = tds.get_cell(cell2_key).unwrap();
let err = Tds::validate_mutual_neighbor_back_reference(cell1_key, cell1, 2, cell2, 2)
.unwrap_err();
assert!(matches!(
err,
TdsError::InvalidNeighbors { message } if message.contains("expected back-reference")
));
}
#[test]
fn test_orientation_validation_rejects_non_periodic_self_neighbor() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let cell_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
{
let cell = tds.get_cell_by_key_mut(cell_key).unwrap();
cell.neighbors = Some(vec![Some(cell_key), None, None].into());
}
let cell = tds.get_cell(cell_key).unwrap();
assert!(!Tds::allows_periodic_self_neighbor(cell));
let err = tds.validate_coherent_orientation().unwrap_err();
assert!(matches!(
err,
TdsError::OrientationViolation {
cell1_key,
cell2_key,
..
} if cell1_key == cell_key && cell2_key == cell_key
));
assert!(!tds.is_coherently_oriented());
let err = tds.normalize_coherent_orientation().unwrap_err();
assert!(matches!(
err,
TdsError::InconsistentDataStructure { message }
if message.contains("Contradictory orientation constraints")
));
}
#[test]
fn test_orientation_validation_allows_periodic_self_neighbor() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let cell_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
{
let cell = tds.get_cell_by_key_mut(cell_key).unwrap();
cell.neighbors = Some(vec![Some(cell_key), None, None].into());
cell.set_periodic_vertex_offsets(vec![[0, 0], [0, 0], [0, 0]]);
}
let cell = tds.get_cell(cell_key).unwrap();
assert!(Tds::allows_periodic_self_neighbor(cell));
assert!(tds.validate_coherent_orientation().is_ok());
assert!(tds.is_coherently_oriented());
assert!(tds.normalize_coherent_orientation().is_ok());
}
#[test]
fn test_orientation_validation_rejects_one_way_neighbor_pointer() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let cell1_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let cell2_key = tds
.insert_cell_with_mapping(Cell::new(vec![v_b, v_a, v_d], None).unwrap())
.unwrap();
tds.assign_neighbors().unwrap();
assert!(tds.validate_coherent_orientation().is_ok());
let mirror_idx = {
let cell1 = tds.get_cell(cell1_key).unwrap();
let neighbors = cell1
.neighbors()
.expect("cell1 should have neighbors after assign_neighbors()");
let facet_idx = neighbors
.iter()
.position(|n| *n == Some(cell2_key))
.expect("cell1 should reference cell2");
let cell2 = tds.get_cell(cell2_key).unwrap();
cell1
.mirror_facet_index(facet_idx, cell2)
.expect("adjacent cells should have a mirror facet")
};
{
let cell2 = tds.get_cell_by_key_mut(cell2_key).unwrap();
let neighbors = cell2
.neighbors
.as_mut()
.expect("cell2 should have neighbors after assign_neighbors()");
neighbors[mirror_idx] = None;
}
let err = tds.validate_coherent_orientation().unwrap_err();
assert!(matches!(
err,
TdsError::InvalidNeighbors { message } if message.contains("expected back-reference")
));
assert!(!tds.is_coherently_oriented());
}
macro_rules! test_normalize_repairs_incoherent_adjacent_pair {
($name:ident, $dim:literal) => {
#[test]
fn $name() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let mut vertex_keys = Vec::with_capacity($dim + 2);
let mut seed = 1.0_f64;
for idx in 0..($dim + 2) {
let mut coords = [0.0_f64; $dim];
if idx < $dim {
coords[idx] = 1.0;
} else {
for coord in &mut coords {
*coord = seed;
seed += 1.0;
}
}
vertex_keys.push(tds.insert_vertex_with_mapping(vertex!(coords)).unwrap());
}
let cell1_vertices: Vec<_> = vertex_keys.iter().take($dim + 1).copied().collect();
let mut cell2_vertices: Vec<_> = vertex_keys.iter().take($dim).copied().collect();
cell2_vertices.push(vertex_keys[$dim + 1]);
let cell1: Cell<f64, (), (), $dim> = Cell::new(cell1_vertices, None).unwrap();
let cell2: Cell<f64, (), (), $dim> = Cell::new(cell2_vertices, None).unwrap();
tds.insert_cell_with_mapping(cell1).unwrap();
tds.insert_cell_with_mapping(cell2).unwrap();
tds.assign_neighbors().unwrap();
let err = tds.validate_coherent_orientation().unwrap_err();
assert!(matches!(err, TdsError::OrientationViolation { .. }));
assert!(!tds.is_coherently_oriented());
tds.normalize_coherent_orientation().unwrap();
assert!(tds.validate_coherent_orientation().is_ok());
assert!(tds.is_coherently_oriented());
}
};
}
test_normalize_repairs_incoherent_adjacent_pair!(
test_normalize_repairs_incoherent_adjacent_pair_2d,
2
);
test_normalize_repairs_incoherent_adjacent_pair!(
test_normalize_repairs_incoherent_adjacent_pair_3d,
3
);
test_normalize_repairs_incoherent_adjacent_pair!(
test_normalize_repairs_incoherent_adjacent_pair_4d,
4
);
test_normalize_repairs_incoherent_adjacent_pair!(
test_normalize_repairs_incoherent_adjacent_pair_5d,
5
);
#[test]
fn test_assign_incident_cells_clears_incident_cell_when_no_cells() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let vkey = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
tds.get_vertex_by_key_mut(vkey).unwrap().incident_cell =
Some(CellKey::from(KeyData::from_ffi(u64::MAX)));
assert!(tds.get_vertex_by_key(vkey).unwrap().incident_cell.is_some());
tds.assign_incident_cells().unwrap();
assert!(tds.get_vertex_by_key(vkey).unwrap().incident_cell.is_none());
}
#[test]
fn test_build_facet_to_cells_map_errors_on_u8_facet_index_overflow() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let cell_key = tds
.insert_cell_with_mapping(Cell::new(vec![a, b, c], None).unwrap())
.unwrap();
{
let cell = tds.get_cell_by_key_mut(cell_key).unwrap();
while cell.number_of_vertices() <= usize::from(u8::MAX) + 1 {
cell.push_vertex_key(a);
}
}
let err = tds.build_facet_to_cells_map().unwrap_err();
assert!(matches!(
err,
TdsError::InconsistentDataStructure { message }
if message.contains("Facet index")
));
}
#[test]
fn test_validate_vertex_and_cell_mappings_detect_inconsistencies() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let cell_key = tds
.insert_cell_with_mapping(Cell::new(vec![a, b, c], None).unwrap())
.unwrap();
assert!(tds.validate_vertex_mappings().is_ok());
assert!(tds.validate_cell_mappings().is_ok());
let uuid_a = tds.get_vertex_by_key(a).unwrap().uuid();
tds.uuid_to_vertex_key.remove(&uuid_a);
assert!(matches!(
tds.validate_vertex_mappings(),
Err(TdsError::MappingInconsistency {
entity: EntityKind::Vertex,
..
})
));
tds.uuid_to_vertex_key.insert(uuid_a, b);
assert!(matches!(
tds.validate_vertex_mappings(),
Err(TdsError::MappingInconsistency {
entity: EntityKind::Vertex,
..
})
));
let uuid_cell = tds.get_cell(cell_key).unwrap().uuid();
tds.uuid_to_cell_key.remove(&uuid_cell);
assert!(matches!(
tds.validate_cell_mappings(),
Err(TdsError::MappingInconsistency {
entity: EntityKind::Cell,
..
})
));
}
#[test]
fn test_validate_cell_vertex_keys_detects_missing_vertices() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let cell_key = tds
.insert_cell_with_mapping(Cell::new(vec![a, b, c], None).unwrap())
.unwrap();
let invalid_vkey = VertexKey::from(KeyData::from_ffi(u64::MAX));
tds.get_cell_by_key_mut(cell_key)
.unwrap()
.push_vertex_key(invalid_vkey);
let err = tds.validate_cell_vertex_keys().unwrap_err();
assert!(matches!(err, TdsError::InconsistentDataStructure { .. }));
let err = tds.is_valid().unwrap_err();
assert!(matches!(
err,
TdsError::InconsistentDataStructure { message }
if message.contains("references non-existent vertex key")
));
}
#[test]
fn test_is_connected_returns_false_for_isolated_cells() {
use crate::core::cell::Cell;
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let a1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let a2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let b0 = tds
.insert_vertex_with_mapping(vertex!([10.0, 0.0]))
.unwrap();
let b1 = tds
.insert_vertex_with_mapping(vertex!([11.0, 0.0]))
.unwrap();
let b2 = tds
.insert_vertex_with_mapping(vertex!([10.0, 1.0]))
.unwrap();
tds.insert_cell_with_mapping(Cell::new(vec![a0, a1, a2], None).unwrap())
.unwrap();
tds.insert_cell_with_mapping(Cell::new(vec![b0, b1, b2], None).unwrap())
.unwrap();
assert!(
!tds.is_connected(),
"TDS with two isolated cells (no neighbor wiring) must not be connected"
);
}
}