#![forbid(unsafe_code)]
use crate::core::collections::{
MAX_PRACTICAL_DIMENSION_SIZE, SimplexKeySet, SmallBuffer, StorageMap, UuidToSimplexKeyMap,
UuidToVertexKeyMap,
};
use crate::core::tds::errors::{NeighborValidationError, TdsError, TriangulationConstructionState};
use crate::core::tds::incidence::VertexIncidenceIndex;
use crate::core::tds::{SimplexKey, VertexKey};
use crate::core::{
facet::facet_key_from_vertices, simplex::Simplex,
util::periodic_facet_key_from_lifted_vertices, vertex::Vertex,
};
use std::{
fmt::Debug,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
};
use uuid::Uuid;
#[derive(Clone, Debug)]
#[must_use]
pub struct TopologyOwnerId {
identity: Arc<Uuid>,
}
impl TopologyOwnerId {
pub(crate) fn from_identity(identity: &Arc<Uuid>) -> Self {
Self {
identity: Arc::clone(identity),
}
}
}
impl PartialEq for TopologyOwnerId {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.identity, &other.identity)
}
}
impl Eq for TopologyOwnerId {}
pub trait TopologyOwner {
fn topology_owner_id(&self) -> TopologyOwnerId;
fn topology_generation(&self) -> u64;
}
#[derive(Debug)]
pub struct Tds<U, V, const D: usize> {
pub(super) vertices: StorageMap<VertexKey, Vertex<U, D>>,
pub(super) simplices: StorageMap<SimplexKey, Simplex<V, D>>,
pub(crate) uuid_to_vertex_key: UuidToVertexKeyMap,
pub(crate) uuid_to_simplex_key: UuidToSimplexKeyMap,
pub(super) vertex_to_simplices: VertexIncidenceIndex,
pub(crate) construction_state: TriangulationConstructionState,
pub(super) generation: Arc<AtomicU64>,
pub(super) identity: Arc<Uuid>,
}
impl<U, V, const D: usize> Clone for Tds<U, V, D>
where
U: Clone,
V: Clone,
{
fn clone(&self) -> Self {
Self {
vertices: self.vertices.clone(),
simplices: self.simplices.clone(),
uuid_to_vertex_key: self.uuid_to_vertex_key.clone(),
uuid_to_simplex_key: self.uuid_to_simplex_key.clone(),
vertex_to_simplices: self.vertex_to_simplices.clone(),
construction_state: self.construction_state.clone(),
generation: Arc::new(AtomicU64::new(self.generation.load(Ordering::Relaxed))),
identity: Arc::new(Uuid::new_v4()),
}
}
}
impl<U, V, const D: usize> Tds<U, V, D>
where
U: Clone,
V: Clone,
{
pub(crate) fn clone_for_rollback(&self) -> Self {
Self {
vertices: self.vertices.clone(),
simplices: self.simplices.clone(),
uuid_to_vertex_key: self.uuid_to_vertex_key.clone(),
uuid_to_simplex_key: self.uuid_to_simplex_key.clone(),
vertex_to_simplices: self.vertex_to_simplices.clone(),
construction_state: self.construction_state.clone(),
generation: Arc::new(AtomicU64::new(self.generation.load(Ordering::Relaxed))),
identity: Arc::clone(&self.identity),
}
}
pub(crate) fn clone_from_for_rollback(&mut self, source: &Self) {
self.vertices.clone_from(&source.vertices);
self.simplices.clone_from(&source.simplices);
self.uuid_to_vertex_key
.clone_from(&source.uuid_to_vertex_key);
self.uuid_to_simplex_key
.clone_from(&source.uuid_to_simplex_key);
self.vertex_to_simplices
.clone_from(&source.vertex_to_simplices);
self.construction_state
.clone_from(&source.construction_state);
self.generation = Arc::new(AtomicU64::new(source.generation.load(Ordering::Relaxed)));
self.identity = Arc::clone(&source.identity);
}
}
impl<U, V, const D: usize> Tds<U, V, D> {
#[inline]
pub(super) fn allows_periodic_self_neighbor(simplex: &Simplex<V, D>) -> bool {
let Some(offsets) = simplex.periodic_vertex_offsets() else {
return false;
};
!offsets.is_empty() && offsets.len() == simplex.number_of_vertices()
}
pub(crate) fn periodic_facet_key_from_simplex_vertices(
simplex: &Simplex<V, D>,
vertices: &[VertexKey],
facet_index: usize,
) -> Result<u64, TdsError> {
if facet_index >= vertices.len() {
return Err(TdsError::IndexOutOfBounds {
index: facet_index,
bound: vertices.len(),
context: format!("facet index for simplex with {} vertices", vertices.len()),
});
}
let Some(periodic_offsets) = simplex.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::DimensionMismatch {
expected: vertices.len(),
actual: periodic_offsets.len(),
context: "simplex periodic offset count vs vertex count".to_string(),
});
}
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 simplex {:?} facet {facet_index}: {error}",
simplex.uuid()
),
},
)
}
pub(super) fn build_periodic_vertex_uuid_offsets(
&self,
simplex_key: SimplexKey,
vertices: &[VertexKey],
) -> Result<SimplexUuidSortKey<D>, TdsError> {
let simplex = self
.simplices
.get(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "building periodic vertex identity (UUID/offset)".to_string(),
})?;
let periodic_offsets = simplex.periodic_vertex_offsets();
if let Some(offsets) = periodic_offsets
&& offsets.len() != vertices.len()
{
return Err(TdsError::DimensionMismatch {
expected: vertices.len(),
actual: offsets.len(),
context: format!("simplex {simplex_key:?} periodic offset count vs vertex count"),
});
}
let mut vertex_uuid_offsets = SimplexUuidSortKey::<D>::new();
for (vertex_idx, &vertex_key) in vertices.iter().enumerate() {
let vertex = self
.vertices
.get(vertex_key)
.ok_or_else(|| TdsError::VertexNotFound {
vertex_key,
context: format!(
"referenced by simplex {simplex_key:?} at index {vertex_idx} while building periodic vertex identity (UUID/offset)",
),
})?;
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();
Ok(vertex_uuid_offsets)
}
pub(super) fn lifted_vertex_identities(
simplex_key: SimplexKey,
simplex: &Simplex<V, D>,
) -> Result<SmallBuffer<(VertexKey, [i8; D]), MAX_PRACTICAL_DIMENSION_SIZE>, TdsError> {
let vertices = simplex.vertices();
let periodic_offsets = simplex.periodic_vertex_offsets();
if let Some(offsets) = periodic_offsets
&& offsets.len() != vertices.len()
{
return Err(TdsError::DimensionMismatch {
expected: vertices.len(),
actual: offsets.len(),
context: format!(
"simplex {simplex_key:?} periodic offset count vs vertex count in neighbor topology validation"
),
});
}
let mut lifted_vertices: SmallBuffer<(VertexKey, [i8; D]), MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::new();
for (vertex_idx, &vertex_key) in vertices.iter().enumerate() {
let offset = periodic_offsets.map_or([0_i8; D], |offsets| offsets[vertex_idx]);
lifted_vertices.push((vertex_key, offset));
}
Ok(lifted_vertices)
}
pub(super) fn matching_lifted_facet_index(
simplex: &Simplex<V, D>,
neighbor: &Simplex<V, D>,
) -> Result<Option<usize>, TdsError> {
let simplex_vertices = simplex.vertices();
let neighbor_vertices = neighbor.vertices();
for simplex_facet_index in 0..simplex_vertices.len() {
let simplex_facet_key = Self::periodic_facet_key_from_simplex_vertices(
simplex,
simplex_vertices,
simplex_facet_index,
)?;
for neighbor_facet_index in 0..neighbor_vertices.len() {
let neighbor_facet_key = Self::periodic_facet_key_from_simplex_vertices(
neighbor,
neighbor_vertices,
neighbor_facet_index,
)?;
if simplex_facet_key == neighbor_facet_key {
return Ok(Some(simplex_facet_index));
}
}
}
Ok(None)
}
pub(super) fn matching_lifted_mirror_facet_index(
simplex: &Simplex<V, D>,
facet_idx: usize,
neighbor: &Simplex<V, D>,
context: &str,
) -> Result<usize, TdsError> {
let simplex_facet_key =
Self::periodic_facet_key_from_simplex_vertices(simplex, simplex.vertices(), facet_idx)?;
let mut mirror_idx = None;
for neighbor_facet_idx in 0..neighbor.vertices().len() {
let neighbor_facet_key = Self::periodic_facet_key_from_simplex_vertices(
neighbor,
neighbor.vertices(),
neighbor_facet_idx,
)?;
if neighbor_facet_key == simplex_facet_key
&& mirror_idx.replace(neighbor_facet_idx).is_some()
{
return Err(TdsError::InvalidNeighbors {
reason: NeighborValidationError::MirrorFacetAmbiguous {
simplex_uuid: simplex.uuid(),
neighbor_uuid: neighbor.uuid(),
},
});
}
}
mirror_idx.ok_or_else(|| TdsError::InvalidNeighbors {
reason: NeighborValidationError::MirrorFacetMissing {
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
neighbor_uuid: neighbor.uuid(),
context: context.to_string(),
},
})
}
pub(crate) fn facet_key_for_simplex_facet(
&self,
simplex_key: SimplexKey,
facet_index: usize,
) -> Result<u64, TdsError> {
let vertices = self.simplex_vertices(simplex_key)?;
let simplex = self
.simplices
.get(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: format!("deriving facet key for index {facet_index}"),
})?;
Self::periodic_facet_key_from_simplex_vertices(simplex, vertices, facet_index)
}
pub fn simplices(&self) -> impl Iterator<Item = (SimplexKey, &Simplex<V, D>)> {
self.simplices.iter()
}
pub fn vertices(&self) -> impl Iterator<Item = (VertexKey, &Vertex<U, D>)> {
self.vertices.iter()
}
pub fn vertex_keys(&self) -> impl Iterator<Item = VertexKey> + '_ {
self.vertices.keys()
}
pub fn simplex_keys(&self) -> impl Iterator<Item = SimplexKey> + '_ {
self.simplices.keys()
}
pub(crate) fn simplex_key_iter(&self) -> slotmap::dense::Keys<'_, SimplexKey, Simplex<V, D>> {
self.simplices.keys()
}
#[must_use]
pub fn simplex(&self, key: SimplexKey) -> Option<&Simplex<V, D>> {
self.simplices.get(key)
}
#[must_use]
pub fn contains_simplex(&self, key: SimplexKey) -> bool {
self.simplices.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)
}
#[inline]
#[must_use]
pub const fn construction_state(&self) -> &TriangulationConstructionState {
&self.construction_state
}
#[inline]
pub(super) fn refresh_incomplete_construction_state(&mut self) {
if matches!(
self.construction_state,
TriangulationConstructionState::Incomplete(_)
) {
self.construction_state =
TriangulationConstructionState::Incomplete(self.vertices.len());
}
}
#[must_use]
pub fn number_of_simplices(&self) -> usize {
self.simplices.len()
}
#[must_use]
pub fn is_connected(&self) -> bool {
let total = self.simplices.len();
if total == 0 {
return true;
}
let Some(start) = self.simplex_keys().next() else {
return true;
};
let mut visited: SimplexKeySet = SimplexKeySet::default();
visited.reserve(total);
let mut stack: Vec<SimplexKey> = Vec::with_capacity(total.min(64));
stack.push(start);
while let Some(ck) = stack.pop() {
if !visited.insert(ck) {
continue;
}
let Some(simplex) = self.simplices.get(ck) else {
continue;
};
let Some(neighbors) = simplex.neighbor_keys() else {
continue;
};
for n_opt in neighbors {
let Some(nk) = n_opt else {
continue;
};
if self.simplices.contains_key(nk) && !visited.contains(&nk) {
stack.push(nk);
}
}
}
visited.len() == total
}
#[inline]
pub(super) 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 fn topology_owner_id(&self) -> TopologyOwnerId {
TopologyOwnerId::from_identity(&self.identity)
}
#[inline]
pub(crate) const fn identity(&self) -> &Arc<Uuid> {
&self.identity
}
#[inline]
pub(crate) fn mark_topology_modified(&self) {
self.bump_generation();
}
}
impl<U, V, const D: usize> Tds<U, V, D> {}
impl<U, V, const D: usize> TopologyOwner for Tds<U, V, D> {
#[inline]
fn topology_owner_id(&self) -> TopologyOwnerId {
Self::topology_owner_id(self)
}
#[inline]
fn topology_generation(&self) -> u64 {
self.generation()
}
}
impl<U, V, const D: usize> Tds<U, V, D> {
#[inline]
pub fn simplex_vertices(&self, simplex_key: SimplexKey) -> Result<&[VertexKey], TdsError> {
let simplex = self
.simplices
.get(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "simplex_vertices lookup".to_string(),
})?;
let simplex_vertices = simplex.vertices();
for (idx, &vertex_key) in simplex_vertices.iter().enumerate() {
if !self.vertices.contains_key(vertex_key) {
return Err(TdsError::VertexNotFound {
vertex_key,
context: format!(
"referenced by simplex {} (key {simplex_key:?}) at position {idx}",
simplex.uuid()
),
});
}
}
Ok(simplex_vertices)
}
#[inline]
#[must_use]
pub fn simplex_key_from_uuid(&self, simplex_uuid: &Uuid) -> Option<SimplexKey> {
self.uuid_to_simplex_key.get(simplex_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 simplex_uuid_from_key(&self, simplex_key: SimplexKey) -> Option<Uuid> {
self.simplices.get(simplex_key).map(Simplex::uuid)
}
#[inline]
#[must_use]
pub fn vertex_uuid_from_key(&self, vertex_key: VertexKey) -> Option<Uuid> {
self.vertices.get(vertex_key).map(Vertex::uuid)
}
#[inline]
#[must_use]
pub(crate) fn simplex_mut(&mut self, simplex_key: SimplexKey) -> Option<&mut Simplex<V, D>> {
self.simplices.get_mut(simplex_key)
}
#[inline]
#[must_use]
pub fn vertex(&self, vertex_key: VertexKey) -> Option<&Vertex<U, D>> {
self.vertices.get(vertex_key)
}
#[inline]
#[must_use]
pub(crate) fn vertex_mut(&mut self, vertex_key: VertexKey) -> Option<&mut Vertex<U, D>> {
self.vertices.get_mut(vertex_key)
}
#[inline]
#[must_use]
pub fn contains_vertex_key(&self, vertex_key: VertexKey) -> bool {
self.vertices.contains_key(vertex_key)
}
#[inline]
pub(super) fn insert_empty_vertex_incidence(
&mut self,
vertex_key: VertexKey,
) -> Result<(), TdsError> {
self.vertex_to_simplices.insert_vertex(vertex_key)
}
#[inline]
pub(super) fn remove_vertex_incidence(
&mut self,
vertex_key: VertexKey,
) -> Result<(), TdsError> {
self.vertex_to_simplices.remove_isolated_vertex(vertex_key)
}
pub(super) fn add_simplex_to_vertex_incidence(
&mut self,
simplex_key: SimplexKey,
vertices: &[VertexKey],
) -> Result<(), TdsError> {
self.vertex_to_simplices
.insert_simplex(simplex_key, vertices)
}
pub(super) fn rebuild_vertex_to_simplices_index(&mut self) -> Result<(), TdsError> {
let mut vertex_to_simplices =
VertexIncidenceIndex::with_vertex_capacity(self.vertices.len());
for vertex_key in self.vertices.keys() {
vertex_to_simplices.insert_vertex(vertex_key)?;
}
for (simplex_key, simplex) in &self.simplices {
vertex_to_simplices.insert_simplex(simplex_key, simplex.vertices())?;
}
self.vertex_to_simplices = vertex_to_simplices;
Ok(())
}
#[inline]
pub(crate) const fn vertex_to_simplices_index(&self) -> &VertexIncidenceIndex {
&self.vertex_to_simplices
}
pub(crate) fn simplex_keys_containing_vertex(
&self,
vertex_key: VertexKey,
) -> impl Iterator<Item = SimplexKey> + '_ {
self.vertex_to_simplices.simplex_keys(vertex_key)
}
#[inline]
pub(crate) fn first_simplex_containing_vertex(
&self,
vertex_key: VertexKey,
) -> Option<SimplexKey> {
self.vertex_to_simplices.first_simplex(vertex_key)
}
}
#[cfg(test)]
mod test_support {
use super::Tds;
use crate::core::collections::PeriodicOffsetBuffer;
use crate::core::tds::{SimplexKey, VertexKey};
impl<U, V, const D: usize> Tds<U, V, D> {
pub(in crate::core) fn clear_vertex_incidence_for_test(&mut self, vertex_key: VertexKey) {
self.vertex_to_simplices.clear_vertex_for_test(vertex_key);
}
pub(in crate::core) fn add_simplex_to_vertex_incidence_for_test(
&mut self,
vertex_key: VertexKey,
simplex_key: SimplexKey,
) {
self.vertex_to_simplices
.insert_simplex(simplex_key, &[vertex_key])
.expect("test helper should receive an existing vertex incidence entry");
}
pub(in crate::core) fn remove_simplex_storage_only_for_test(
&mut self,
simplex_key: SimplexKey,
) {
self.simplices.remove(simplex_key);
self.uuid_to_simplex_key
.retain(|_, mapped_key| *mapped_key != simplex_key);
}
pub(crate) fn push_first_simplex_vertex_key_storage_only_for_test(
&mut self,
vertex_key: VertexKey,
) {
if let Some(simplex) = self.simplices.values_mut().next() {
simplex.push_vertex_key(vertex_key);
}
}
pub(crate) fn remove_vertex_storage_only_for_test(&mut self, vertex_key: VertexKey) {
if let Some(vertex) = self.vertices.remove(vertex_key) {
let vertex_uuid = vertex.uuid();
self.uuid_to_vertex_key
.retain(|uuid, mapped_key| *uuid != vertex_uuid && *mapped_key != vertex_key);
}
}
pub(crate) fn set_first_simplex_periodic_offsets_storage_only_for_test(
&mut self,
offsets: Option<PeriodicOffsetBuffer<D>>,
) {
if let Some(simplex) = self.simplices.values_mut().next() {
simplex.periodic_vertex_offsets = offsets;
}
}
}
}
impl<U, V, const D: usize> Tds<U, V, D> {
#[must_use]
pub fn empty() -> Self {
Self {
vertices: StorageMap::with_key(),
simplices: StorageMap::with_key(),
uuid_to_vertex_key: UuidToVertexKeyMap::default(),
uuid_to_simplex_key: UuidToSimplexKeyMap::default(),
vertex_to_simplices: VertexIncidenceIndex::default(),
construction_state: TriangulationConstructionState::Incomplete(0),
generation: Arc::new(AtomicU64::new(0)),
identity: Arc::new(Uuid::new_v4()),
}
}
}
pub(super) type SimplexUuidSortKey<const D: usize> =
SmallBuffer<(Uuid, [i8; D]), MAX_PRACTICAL_DIMENSION_SIZE>;
#[cfg(test)]
mod tests {
use super::*;
use crate::core::simplex::Simplex;
use crate::core::tds::TdsRollbackTransaction;
use crate::vertex;
use std::assert_matches;
use std::sync::Arc;
fn insert_test_vertex<const D: usize>(tds: &mut Tds<(), (), D>, coordinate: f64) -> VertexKey {
let vertex = vertex!([coordinate; D]).unwrap();
tds.insert_vertex_with_mapping(vertex).unwrap()
}
#[test]
fn test_empty_initializes_storage_identity_and_counts() {
let tds: Tds<(), (), 3> = Tds::empty();
assert_eq!(tds.number_of_vertices(), 0);
assert_eq!(tds.number_of_simplices(), 0);
assert_eq!(tds.dim(), -1);
assert!(tds.vertices().next().is_none());
assert!(tds.simplices().next().is_none());
assert!(tds.vertex_to_simplices_index().is_empty());
assert!(tds.vertex_key_from_uuid(&Uuid::new_v4()).is_none());
assert!(tds.simplex_key_from_uuid(&Uuid::new_v4()).is_none());
assert_matches!(
tds.construction_state(),
TriangulationConstructionState::Incomplete(0)
);
}
#[test]
fn test_incomplete_construction_state_tracks_vertex_count() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let v0 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
assert_matches!(
tds.construction_state(),
TriangulationConstructionState::Incomplete(1)
);
let _v1 = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0]).unwrap())
.unwrap();
assert_matches!(
tds.construction_state(),
TriangulationConstructionState::Incomplete(2)
);
tds.remove_isolated_vertex(v0).unwrap();
assert_matches!(
tds.construction_state(),
TriangulationConstructionState::Incomplete(1)
);
}
#[test]
fn test_vertex_to_simplices_index_tracks_simplex_insertion() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let v0 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
let v1 = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0]).unwrap())
.unwrap();
let v2 = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0]).unwrap())
.unwrap();
for vertex_key in [v0, v1, v2] {
assert!(tds.vertex_to_simplices_index().contains_vertex(vertex_key));
assert_eq!(
tds.vertex_to_simplices_index()
.number_of_simplices(vertex_key),
0
);
}
let simplex_key = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.unwrap();
for vertex_key in [v0, v1, v2] {
let simplices: SimplexKeySet = tds.simplex_keys_containing_vertex(vertex_key).collect();
assert_eq!(simplices.len(), 1);
assert!(simplices.contains(&simplex_key));
}
assert!(
tds.simplex_keys_containing_vertex(VertexKey::default())
.next()
.is_none()
);
}
#[test]
fn test_vertex_to_simplices_index_returns_disconnected_vertex_star() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let shared = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
let v1 = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0]).unwrap())
.unwrap();
let v2 = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0]).unwrap())
.unwrap();
let v3 = tds
.insert_vertex_with_mapping(vertex!([-1.0, 0.0]).unwrap())
.unwrap();
let v4 = tds
.insert_vertex_with_mapping(vertex!([0.0, -1.0]).unwrap())
.unwrap();
let first = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![shared, v1, v2], None).unwrap(),
)
.unwrap();
let second = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![shared, v3, v4], None).unwrap(),
)
.unwrap();
tds.vertex_mut(shared)
.unwrap()
.set_incident_simplex(Some(first));
let simplices: SimplexKeySet = tds.simplex_keys_containing_vertex(shared).collect();
assert_eq!(simplices.len(), 2);
assert!(simplices.contains(&first));
assert!(simplices.contains(&second));
}
#[test]
fn test_facet_key_for_simplex_facet_maps_periodic_derivation_errors() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let v_a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
let v_b = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0]).unwrap())
.unwrap();
let v_c = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0]).unwrap())
.unwrap();
let simplex_key = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(),
)
.unwrap();
tds.simplex_mut(simplex_key)
.unwrap()
.set_periodic_vertex_offsets(vec![[-128_i8, 0_i8], [127_i8, 0_i8], [0_i8, 0_i8]])
.unwrap();
let err = tds.facet_key_for_simplex_facet(simplex_key, 2).unwrap_err();
assert_matches!(
err,
TdsError::InconsistentDataStructure { message }
if message.contains("Failed to derive periodic facet key")
&& message.contains("facet 2")
);
}
#[test]
fn test_generation_counter_bumps_on_topology_modification() {
let mut tds: Tds<(), (), 2> = Tds::empty();
assert_eq!(tds.generation(), 0);
let _v = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
assert!(tds.generation() > 0);
let gen_before = tds.generation();
tds.mark_topology_modified();
assert!(tds.generation() > gen_before);
}
#[test]
fn test_simplex_vertices_errors_on_missing_vertex_key() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let v0 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
let v1 = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0]).unwrap())
.unwrap();
let v2 = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0]).unwrap())
.unwrap();
let ck = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.unwrap();
tds.vertices.remove(v2);
tds.uuid_to_vertex_key.retain(|_, &mut vk| vk != v2);
let err = tds.simplex_vertices(ck).unwrap_err();
assert_matches!(err, TdsError::VertexNotFound { .. });
}
#[test]
fn test_mark_topology_modified_bumps_generation() {
let tds: Tds<(), (), 2> = Tds::empty();
let gen_before = tds.generation();
tds.mark_topology_modified();
assert_eq!(tds.generation(), gen_before + 1);
}
macro_rules! test_clone_identity_dimensions {
($($dim:expr),+ $(,)?) => {
pastey::paste! {
$(
#[test]
fn [<test_clone_uses_fresh_runtime_identity_ $dim d>]() {
let tds: Tds<(), (), $dim> = Tds::empty();
let cloned = tds.clone();
assert!(
!Arc::ptr_eq(tds.identity(), cloned.identity()),
"ordinary TDS clones must have distinct runtime identities"
);
assert_eq!(tds.generation(), cloned.generation());
}
#[test]
fn [<test_clone_for_rollback_preserves_identity_with_independent_generation_ $dim d>]() {
let mut tds: Tds<(), (), $dim> = Tds::empty();
let _v = tds
.insert_vertex_with_mapping(vertex!([0.0_f64; $dim]).unwrap())
.unwrap();
let snapshot = tds.clone_for_rollback();
let snapshot_generation = snapshot.generation();
assert!(
Arc::ptr_eq(tds.identity(), snapshot.identity()),
"rollback snapshots should preserve runtime identity"
);
tds.mark_topology_modified();
assert_eq!(
snapshot.generation(),
snapshot_generation,
"rollback snapshots need an independent generation counter"
);
}
#[test]
fn [<test_clone_from_for_rollback_replaces_storage_and_preserves_identity_ $dim d>]() {
let mut source: Tds<(), (), $dim> = Tds::empty();
let source_vertex = source
.insert_vertex_with_mapping(vertex!([0.0_f64; $dim]).unwrap())
.unwrap();
let source_generation = source.generation();
let mut target: Tds<(), (), $dim> = Tds::empty();
let _stale_vertex = target
.insert_vertex_with_mapping(vertex!([1.0_f64; $dim]).unwrap())
.unwrap();
let _extra_stale_vertex = target
.insert_vertex_with_mapping(vertex!([2.0_f64; $dim]).unwrap())
.unwrap();
assert!(
!Arc::ptr_eq(source.identity(), target.identity()),
"source and scratch storage should start with distinct identities"
);
target.clone_from_for_rollback(&source);
assert!(
Arc::ptr_eq(source.identity(), target.identity()),
"rollback scratch storage should adopt the source runtime identity"
);
assert_eq!(target.generation(), source_generation);
assert_eq!(target.number_of_vertices(), source.number_of_vertices());
assert_eq!(target.number_of_simplices(), source.number_of_simplices());
assert!(target.vertex(source_vertex).is_some());
source.mark_topology_modified();
assert_eq!(
target.generation(),
source_generation,
"clone_from_for_rollback must keep an independent generation counter"
);
}
#[test]
fn [<test_rollback_transaction_drop_restores_snapshot_ $dim d>]() {
let mut tds: Tds<(), (), $dim> = Tds::empty();
let source_vertex = insert_test_vertex(&mut tds, 0.0);
let source_generation = tds.generation();
let source_identity = Arc::clone(tds.identity());
{
let mut transaction = TdsRollbackTransaction::begin(&mut tds);
let _transient_vertex = insert_test_vertex(transaction.tds_mut(), 1.0);
}
assert!(Arc::ptr_eq(&source_identity, tds.identity()));
assert_eq!(tds.generation(), source_generation);
assert_eq!(tds.number_of_vertices(), 1);
assert!(tds.vertex(source_vertex).is_some());
}
#[test]
fn [<test_rollback_transaction_explicit_rollback_restores_snapshot_ $dim d>]() {
let mut tds: Tds<(), (), $dim> = Tds::empty();
let source_vertex = insert_test_vertex(&mut tds, 0.0);
let source_generation = tds.generation();
{
let mut transaction = TdsRollbackTransaction::begin(&mut tds);
let _transient_vertex = insert_test_vertex(transaction.tds_mut(), 1.0);
transaction.rollback();
}
assert_eq!(tds.generation(), source_generation);
assert_eq!(tds.number_of_vertices(), 1);
assert!(tds.vertex(source_vertex).is_some());
}
#[test]
fn [<test_rollback_transaction_restore_keeps_transaction_open_ $dim d>]() {
let mut tds: Tds<(), (), $dim> = Tds::empty();
let source_vertex = insert_test_vertex(&mut tds, 0.0);
let committed_vertex = {
let mut transaction = TdsRollbackTransaction::begin(&mut tds);
let _transient_vertex = insert_test_vertex(transaction.tds_mut(), 1.0);
transaction.restore();
let committed_vertex = insert_test_vertex(transaction.tds_mut(), 2.0);
transaction.commit();
committed_vertex
};
assert_eq!(tds.number_of_vertices(), 2);
assert!(tds.vertex(source_vertex).is_some());
assert!(tds.vertex(committed_vertex).is_some());
}
#[test]
fn [<test_rollback_transaction_commit_preserves_mutation_ $dim d>]() {
let mut tds: Tds<(), (), $dim> = Tds::empty();
let source_vertex = insert_test_vertex(&mut tds, 0.0);
let committed_vertex = {
let mut transaction = TdsRollbackTransaction::begin(&mut tds);
let committed_vertex = insert_test_vertex(transaction.tds_mut(), 1.0);
transaction.commit();
committed_vertex
};
assert_eq!(tds.number_of_vertices(), 2);
assert!(tds.vertex(source_vertex).is_some());
assert!(tds.vertex(committed_vertex).is_some());
}
)+
}
};
}
test_clone_identity_dimensions!(2, 3, 4, 5);
}