use super::errors::{
EntityKind, NeighborValidationError, TdsConstructionError, TdsError, TdsMutationError,
TriangulationConstructionState,
};
use super::incidence::SimplexIncidenceRemoval;
use super::model::{SimplexUuidSortKey, Tds, UnverifiedTds};
use super::{SimplexKey, TdsRollbackTransaction, VertexKey};
use crate::core::collections::{
CLEANUP_OPERATION_BUFFER_SIZE, Entry, FastHashMap, MAX_PRACTICAL_DIMENSION_SIZE, SimplexKeySet,
SimplexRemovalBuffer, SmallBuffer, VertexKeySet, fast_hash_map_with_capacity,
fast_hash_set_with_capacity,
};
use crate::core::simplex::{NeighborSlot, Simplex, SimplexValidationError};
use crate::core::vertex::Vertex;
use crate::refinement::RefinementError;
use std::collections::VecDeque;
#[derive(Clone, Copy)]
enum SimplexInsertionTopologyCheck {
Checked,
Prechecked,
}
type SimplexRemovalRecord = (
SimplexKey,
SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
);
type SimplexRemovalRecords = SmallBuffer<SimplexRemovalRecord, CLEANUP_OPERATION_BUFFER_SIZE>;
type SimplexIncidenceRemovalRecords =
SmallBuffer<SimplexIncidenceRemoval, CLEANUP_OPERATION_BUFFER_SIZE>;
type IncidentSimplexUpdates =
SmallBuffer<(VertexKey, Option<SimplexKey>), CLEANUP_OPERATION_BUFFER_SIZE>;
enum RemovedSimplexMembership<'a> {
Inline(&'a [SimplexRemovalRecord]),
Hashed(SimplexKeySet),
}
impl<'a> RemovedSimplexMembership<'a> {
fn from_removals(simplex_removals: &'a [SimplexRemovalRecord]) -> Self {
if simplex_removals.len() <= CLEANUP_OPERATION_BUFFER_SIZE {
return Self::Inline(simplex_removals);
}
let mut simplex_keys = fast_hash_set_with_capacity(simplex_removals.len());
simplex_keys.extend(simplex_removals.iter().map(|(simplex_key, _)| *simplex_key));
Self::Hashed(simplex_keys)
}
fn contains(&self, simplex_key: SimplexKey) -> bool {
match self {
Self::Inline(simplex_removals) => simplex_removals
.iter()
.any(|(removed_key, _)| *removed_key == simplex_key),
Self::Hashed(simplex_keys) => simplex_keys.contains(&simplex_key),
}
}
}
enum AffectedVertexRecords {
Inline(SmallBuffer<VertexKey, CLEANUP_OPERATION_BUFFER_SIZE>),
Hashed(VertexKeySet),
}
impl AffectedVertexRecords {
fn with_expected_len(expected_len: usize) -> Self {
if expected_len <= CLEANUP_OPERATION_BUFFER_SIZE {
return Self::Inline(SmallBuffer::with_capacity(expected_len));
}
Self::Hashed(fast_hash_set_with_capacity(expected_len))
}
fn insert(&mut self, vertex_key: VertexKey) {
match self {
Self::Inline(vertices) => {
if !vertices.contains(&vertex_key) {
vertices.push(vertex_key);
}
}
Self::Hashed(vertices) => {
vertices.insert(vertex_key);
}
}
}
fn len(&self) -> usize {
match self {
Self::Inline(vertices) => vertices.len(),
Self::Hashed(vertices) => vertices.len(),
}
}
fn try_for_each<E>(&self, mut visit: impl FnMut(VertexKey) -> Result<(), E>) -> Result<(), E> {
match self {
Self::Inline(vertices) => {
for &vertex_key in vertices {
visit(vertex_key)?;
}
}
Self::Hashed(vertices) => {
for &vertex_key in vertices {
visit(vertex_key)?;
}
}
}
Ok(())
}
}
#[expect(
clippy::large_enum_variant,
reason = "inline small-cavity candidate storage avoids hot-path allocations; large batches use the hash-backed variant"
)]
enum CandidateIncidentRecords {
Inline(SmallBuffer<(VertexKey, SimplexKey), CLEANUP_OPERATION_BUFFER_SIZE>),
Hashed(FastHashMap<VertexKey, SimplexKey>),
}
impl CandidateIncidentRecords {
fn with_expected_len(expected_len: usize) -> Self {
if expected_len <= CLEANUP_OPERATION_BUFFER_SIZE {
return Self::Inline(SmallBuffer::with_capacity(expected_len));
}
Self::Hashed(fast_hash_map_with_capacity(expected_len))
}
fn insert_if_absent(&mut self, vertex_key: VertexKey, simplex_key: SimplexKey) {
match self {
Self::Inline(candidates) => {
if !candidates
.iter()
.any(|(candidate_vertex, _)| *candidate_vertex == vertex_key)
{
candidates.push((vertex_key, simplex_key));
}
}
Self::Hashed(candidates) => {
candidates.entry(vertex_key).or_insert(simplex_key);
}
}
}
fn get(&self, vertex_key: VertexKey) -> Option<SimplexKey> {
match self {
Self::Inline(candidates) => {
candidates
.iter()
.find_map(|(candidate_vertex, simplex_key)| {
(*candidate_vertex == vertex_key).then_some(*simplex_key)
})
}
Self::Hashed(candidates) => candidates.get(&vertex_key).copied(),
}
}
}
impl<U, V, const D: usize> Tds<U, V, D> {
pub(crate) fn assign_neighbors(&mut self) -> Result<(), TdsError> {
type FacetInfo = (SimplexKey, usize);
let cap = self.simplices.len().saturating_mul(D.saturating_add(1));
let mut facet_map: FastHashMap<u64, SmallBuffer<FacetInfo, 2>> =
fast_hash_map_with_capacity(cap);
for (simplex_key, simplex) in self.simplices.iter() {
let vertices = self.simplex_vertices(simplex_key)?;
for i in 0..vertices.len() {
let facet_key =
Self::periodic_facet_key_from_simplex_vertices(simplex, vertices, i)?;
let facet_entry = facet_map.entry(facet_key).or_default();
if facet_entry.len() >= 2 {
return Err(TdsError::FacetSharingViolation {
facet_key,
existing_incident_count: facet_entry.len(),
attempted_incident_count: facet_entry.len().saturating_add(1),
max_incident_count: 2,
candidate_simplex_uuid: simplex.uuid(),
candidate_facet_index: i,
});
}
facet_entry.push((simplex_key, i));
}
}
let mut simplex_neighbors: FastHashMap<
SimplexKey,
SmallBuffer<Option<SimplexKey>, MAX_PRACTICAL_DIMENSION_SIZE>,
> = fast_hash_map_with_capacity(self.simplices.len());
for (simplex_key, simplex) in self.simplices.iter() {
let vertex_count = simplex.number_of_vertices();
if vertex_count > MAX_PRACTICAL_DIMENSION_SIZE {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"simplex {} vertex count ({vertex_count}) exceeds storage limit MAX_PRACTICAL_DIMENSION_SIZE ({MAX_PRACTICAL_DIMENSION_SIZE}) (would overflow neighbors buffer)",
simplex.uuid(),
),
});
}
let mut neighbors = SmallBuffer::with_capacity(vertex_count);
neighbors.resize(vertex_count, None);
simplex_neighbors.insert(simplex_key, neighbors);
}
for (_facet_key, facet_infos) in facet_map {
if facet_infos.len() != 2 {
continue;
}
let (simplex_key1, vertex_index1) = facet_infos[0];
let (simplex_key2, vertex_index2) = facet_infos[1];
simplex_neighbors.get_mut(&simplex_key1).ok_or_else(|| {
TdsError::SimplexNotFound {
simplex_key: simplex_key1,
context: "assign_neighbors: simplex missing from local neighbors map"
.to_string(),
}
})?[vertex_index1] = Some(simplex_key2);
simplex_neighbors.get_mut(&simplex_key2).ok_or_else(|| {
TdsError::SimplexNotFound {
simplex_key: simplex_key2,
context: "assign_neighbors: simplex missing from local neighbors map"
.to_string(),
}
})?[vertex_index2] = Some(simplex_key1);
}
for (simplex_key, neighbors) in &simplex_neighbors {
self.journal_simplex_before_write(*simplex_key);
if let Some(simplex) = self.simplices.get_mut(*simplex_key) {
let simplex_id = simplex.uuid();
simplex
.set_neighbors_from_keys(neighbors.iter().copied())
.map_err(|source| TdsError::InvalidSimplex { simplex_id, source })?;
}
}
self.bump_generation();
Ok(())
}
pub(crate) fn insert_vertex_with_mapping(
&mut self,
vertex: Vertex<U, D>,
) -> Result<VertexKey, TdsConstructionError> {
let vertex_uuid = vertex.uuid();
if self.uuid_to_vertex_key.contains_key(&vertex_uuid) {
return Err(TdsConstructionError::DuplicateUuid {
entity: EntityKind::Vertex,
uuid: vertex_uuid,
});
}
let vertex_key = self.vertices.insert(vertex);
self.journal_incidence_before_write([vertex_key]);
if let Err(source) = self.insert_empty_vertex_incidence(vertex_key) {
self.vertices.remove(vertex_key);
return Err(TdsConstructionError::ValidationError { source });
}
self.uuid_to_vertex_key.insert(vertex_uuid, vertex_key);
self.journal_inserted_vertex(vertex_key, vertex_uuid);
self.refresh_incomplete_construction_state();
self.bump_generation();
Ok(vertex_key)
}
pub(crate) fn insert_simplex_with_mapping(
&mut self,
simplex: Simplex<V, D>,
) -> Result<SimplexKey, TdsConstructionError> {
self.insert_simplex_with_mapping_impl(simplex, SimplexInsertionTopologyCheck::Checked)
}
pub(crate) fn insert_simplex_with_mapping_trusted_vertices(
&mut self,
simplex: Simplex<V, D>,
) -> Result<SimplexKey, TdsConstructionError> {
self.insert_simplex_with_mapping_impl(simplex, SimplexInsertionTopologyCheck::Checked)
}
pub(crate) fn insert_simplex_with_mapping_prechecked_topology(
&mut self,
simplex: Simplex<V, D>,
) -> Result<SimplexKey, TdsConstructionError> {
self.insert_simplex_with_mapping_impl(simplex, SimplexInsertionTopologyCheck::Prechecked)
}
fn insert_simplex_with_mapping_impl(
&mut self,
simplex: Simplex<V, D>,
topology_check: SimplexInsertionTopologyCheck,
) -> Result<SimplexKey, TdsConstructionError> {
if simplex.number_of_vertices() != D + 1 {
return Err(TdsConstructionError::ValidationError {
source: TdsError::DimensionMismatch {
expected: D + 1,
actual: simplex.number_of_vertices(),
context: format!("{D}-dimensional simplex vertex count"),
},
});
}
let simplex_uuid = simplex.uuid();
if self.uuid_to_simplex_key.contains_key(&simplex_uuid) {
return Err(TdsConstructionError::DuplicateUuid {
entity: EntityKind::Simplex,
uuid: simplex_uuid,
});
}
match topology_check {
SimplexInsertionTopologyCheck::Checked => {
self.validate_simplex_vertices_exist(&simplex)?;
self.validate_simplex_topology_safe_for_insertion(&simplex)?;
}
SimplexInsertionTopologyCheck::Prechecked => {}
}
let simplex_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
simplex.vertices().iter().copied().collect();
let simplex_key = self.simplices.insert(simplex);
self.uuid_to_simplex_key.insert(simplex_uuid, simplex_key);
self.journal_incidence_before_write(simplex_vertices.iter().copied());
if let Err(source) = self.add_simplex_to_vertex_incidence(simplex_key, &simplex_vertices) {
self.simplices.remove(simplex_key);
self.uuid_to_simplex_key.remove(&simplex_uuid);
return Err(TdsConstructionError::ValidationError { source });
}
self.journal_inserted_simplex(simplex_key, simplex_uuid);
self.bump_generation();
Ok(simplex_key)
}
fn validate_simplex_topology_safe_for_insertion(
&self,
simplex: &Simplex<V, D>,
) -> Result<(), TdsConstructionError> {
self.validate_no_duplicate_simplex_on_insert(simplex)?;
self.validate_facet_sharing_on_insert(simplex)?;
Ok(())
}
fn candidate_periodic_vertex_uuid_offsets(
&self,
simplex: &Simplex<V, D>,
) -> Result<SimplexUuidSortKey<D>, 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!(
"candidate simplex {} periodic offset count vs vertex count",
simplex.uuid()
),
});
}
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 candidate simplex {} at index {vertex_idx} while building periodic vertex identity (UUID/offset)",
simplex.uuid()
),
})?;
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)
}
fn validate_no_duplicate_simplex_on_insert(
&self,
simplex: &Simplex<V, D>,
) -> Result<(), TdsError> {
let candidate_identity = self.candidate_periodic_vertex_uuid_offsets(simplex)?;
for (existing_simplex_key, existing_simplex) in self.simplices.iter() {
let vertices = self.simplex_vertices(existing_simplex_key)?;
let existing_identity =
self.build_periodic_vertex_uuid_offsets(existing_simplex_key, vertices)?;
if existing_identity == candidate_identity {
return Err(TdsError::DuplicateSimplices {
simplex_pairs: vec![[existing_simplex.uuid(), simplex.uuid()]],
message: format!(
"Refusing to insert duplicate simplex {} with same vertex UUIDs as existing simplex {existing_simplex_key:?}: {candidate_identity:?}",
simplex.uuid()
),
});
}
}
Ok(())
}
fn validate_facet_sharing_on_insert(&self, simplex: &Simplex<V, D>) -> Result<(), TdsError> {
let vertices = simplex.vertices();
for candidate_facet_idx in 0..vertices.len() {
let candidate_facet_key = Self::periodic_facet_key_from_simplex_vertices(
simplex,
vertices,
candidate_facet_idx,
)?;
let mut incident_count = 0_usize;
for (existing_simplex_key, existing_simplex) in self.simplices.iter() {
let existing_vertices = self.simplex_vertices(existing_simplex_key)?;
for existing_facet_idx in 0..existing_vertices.len() {
let existing_facet_key = Self::periodic_facet_key_from_simplex_vertices(
existing_simplex,
existing_vertices,
existing_facet_idx,
)?;
if existing_facet_key == candidate_facet_key {
incident_count += 1;
if incident_count >= 2 {
return Err(TdsError::FacetSharingViolation {
facet_key: candidate_facet_key,
existing_incident_count: incident_count,
attempted_incident_count: incident_count + 1,
max_incident_count: 2,
candidate_simplex_uuid: simplex.uuid(),
candidate_facet_index: candidate_facet_idx,
});
}
}
}
}
}
Ok(())
}
fn validate_simplex_vertices_exist(
&self,
simplex: &Simplex<V, D>,
) -> Result<(), TdsConstructionError> {
for &vkey in simplex.vertices() {
if !self.vertices.contains_key(vkey) {
return Err(TdsConstructionError::ValidationError {
source: TdsError::VertexNotFound {
vertex_key: vkey,
context: "referenced by simplex being inserted".to_string(),
},
});
}
}
Ok(())
}
#[inline]
pub fn set_vertex_data(
&mut self,
key: VertexKey,
data: Option<U>,
) -> Result<Option<U>, TdsMutationError> {
let vertex = self.vertices.get_mut(key).ok_or_else(|| {
TdsMutationError::from(TdsError::VertexNotFound {
vertex_key: key,
context: "set_vertex_data".to_string(),
})
})?;
let previous = vertex.data.take();
vertex.data = data;
Ok(previous)
}
#[inline]
pub fn set_simplex_data(
&mut self,
key: SimplexKey,
data: Option<V>,
) -> Result<Option<V>, TdsMutationError> {
let simplex = self.simplices.get_mut(key).ok_or_else(|| {
TdsMutationError::from(TdsError::SimplexNotFound {
simplex_key: key,
context: "set_simplex_data".to_string(),
})
})?;
let previous = simplex.data.take();
simplex.data = data;
Ok(previous)
}
#[inline]
pub(crate) fn fill_simplex_data<F>(&mut self, mut data_for: F)
where
F: FnMut(SimplexKey, &Simplex<V, D>) -> V,
{
for (simplex_key, simplex) in self.simplices.iter_mut() {
let data = data_for(simplex_key, simplex);
simplex.data = Some(data);
}
}
pub fn remove_simplices_by_keys(
&mut self,
simplex_keys: &[SimplexKey],
) -> Result<usize, TdsMutationError> {
if simplex_keys.is_empty() {
return Ok(0);
}
let simplex_removals = self.collect_existing_simplex_removals(simplex_keys);
if simplex_removals.is_empty() {
return Ok(0);
}
let simplices_to_remove = RemovedSimplexMembership::from_removals(&simplex_removals);
let (affected_vertices, candidate_incident) =
self.collect_incident_repair_frontier(&simplex_removals, &simplices_to_remove);
let incidence_removals = {
self.journal_incidence_before_write(
simplex_removals
.iter()
.flat_map(|(_, vertices)| vertices.iter().copied()),
);
self.remove_simplices_from_vertex_incidence_transactionally(&simplex_removals)?
};
let incident_updates = match self.plan_incident_simplex_repairs(
&affected_vertices,
&simplices_to_remove,
&candidate_incident,
) {
Ok(incident_updates) => incident_updates,
Err(err) => {
self.rollback_incidence_removals(&incidence_removals);
return Err(err);
}
};
self.clear_surviving_neighbor_backrefs(&simplex_removals, &simplices_to_remove);
let removed_count = self.remove_simplices_and_update_uuid_mappings(&simplex_removals);
if removed_count == 0 {
return Ok(0);
}
self.apply_incident_simplex_updates(incident_updates);
self.bump_generation();
Ok(removed_count)
}
fn remove_simplices_from_vertex_incidence_transactionally(
&mut self,
simplex_removals: &[SimplexRemovalRecord],
) -> Result<SimplexIncidenceRemovalRecords, TdsMutationError> {
let mut incidence_removals = SimplexIncidenceRemovalRecords::new();
for (simplex_key, vertices) in simplex_removals {
match self
.vertex_to_simplices
.remove_simplex(*simplex_key, vertices)
{
Ok(removal) => incidence_removals.push(removal),
Err(source) => {
for removal in incidence_removals.iter().rev() {
self.vertex_to_simplices.rollback_removed_simplex(removal);
}
return Err(source.into());
}
}
}
Ok(incidence_removals)
}
fn rollback_incidence_removals(&mut self, incidence_removals: &SimplexIncidenceRemovalRecords) {
for removal in incidence_removals.iter().rev() {
self.vertex_to_simplices.rollback_removed_simplex(removal);
}
}
fn collect_existing_simplex_removals(
&self,
simplex_keys: &[SimplexKey],
) -> SimplexRemovalRecords {
let mut seen_large = (simplex_keys.len() > CLEANUP_OPERATION_BUFFER_SIZE)
.then(|| fast_hash_set_with_capacity(simplex_keys.len()));
let mut simplex_removals = SimplexRemovalRecords::new();
for &simplex_key in simplex_keys {
let duplicate = seen_large.as_mut().map_or_else(
|| {
simplex_removals
.iter()
.any(|(removed_key, _)| *removed_key == simplex_key)
},
|seen| !seen.insert(simplex_key),
);
if duplicate {
continue;
}
let Some(simplex) = self.simplices.get(simplex_key) else {
continue;
};
let vertices = simplex.vertices().iter().copied().collect();
simplex_removals.push((simplex_key, vertices));
}
simplex_removals
}
fn collect_incident_repair_frontier(
&self,
simplex_removals: &[SimplexRemovalRecord],
simplices_to_remove: &RemovedSimplexMembership<'_>,
) -> (AffectedVertexRecords, CandidateIncidentRecords) {
let expected_vertices = simplex_removals.len().saturating_mul(D.saturating_add(1));
let mut affected_vertices = AffectedVertexRecords::with_expected_len(expected_vertices);
let mut candidate_incident = CandidateIncidentRecords::with_expected_len(expected_vertices);
for (simplex_key, vertices) in simplex_removals {
for &vertex_key in vertices {
affected_vertices.insert(vertex_key);
}
let Some(simplex) = self.simplices.get(*simplex_key) else {
continue;
};
if let Some(simplex_neighbors) = simplex.neighbor_keys() {
for (facet_idx, neighbor_key_opt) in simplex_neighbors.enumerate() {
let Some(neighbor_key) = neighbor_key_opt else {
continue;
};
if simplices_to_remove.contains(neighbor_key) {
continue;
}
for (vertex_index, &vkey) in vertices.iter().enumerate() {
if vertex_index == facet_idx {
continue;
}
candidate_incident.insert_if_absent(vkey, neighbor_key);
}
}
}
}
(affected_vertices, candidate_incident)
}
fn clear_surviving_neighbor_backrefs(
&mut self,
simplex_removals: &[SimplexRemovalRecord],
simplices_to_remove: &RemovedSimplexMembership<'_>,
) {
for (simplex_key, _) in simplex_removals {
let Some(simplex) = self.simplices.get(*simplex_key) else {
continue;
};
let mut neighbors: SmallBuffer<Option<SimplexKey>, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::new();
if let Some(simplex_neighbors) = simplex.neighbor_keys() {
neighbors.extend(simplex_neighbors);
}
for neighbor_key in neighbors.into_iter().flatten() {
if simplices_to_remove.contains(neighbor_key) {
continue; }
self.journal_simplex_before_write(neighbor_key);
let Some(neighbor_simplex) = self.simplices.get_mut(neighbor_key) else {
continue;
};
let Some(neighbors_buf) = neighbor_simplex.neighbor_slots_mut() else {
continue;
};
for slot in neighbors_buf.iter_mut() {
if slot.simplex_key() == Some(*simplex_key) {
*slot = NeighborSlot::Boundary;
}
}
}
}
}
fn remove_simplices_and_update_uuid_mappings(
&mut self,
simplex_removals: &[SimplexRemovalRecord],
) -> usize {
let mut removed_count = 0;
for (simplex_key, _) in simplex_removals {
if let Some(removed_uuid) = self.remove_simplex_storage_transactionally(*simplex_key) {
self.uuid_to_simplex_key.remove(&removed_uuid);
removed_count += 1;
}
}
removed_count
}
fn plan_incident_simplex_repairs(
&self,
affected_vertices: &AffectedVertexRecords,
simplices_to_remove: &RemovedSimplexMembership<'_>,
candidate_incident: &CandidateIncidentRecords,
) -> Result<IncidentSimplexUpdates, TdsMutationError> {
let mut incident_updates = IncidentSimplexUpdates::with_capacity(affected_vertices.len());
affected_vertices.try_for_each(|vk| {
if let Some(update) =
self.incident_simplex_repair_update(vk, simplices_to_remove, candidate_incident)?
{
incident_updates.push(update);
}
Ok::<(), TdsMutationError>(())
})?;
Ok(incident_updates)
}
fn apply_incident_simplex_updates(&mut self, incident_updates: IncidentSimplexUpdates) {
for (vk, new_incident) in incident_updates {
self.journal_vertex_before_write(vk);
if let Some(vertex) = self.vertices.get_mut(vk) {
vertex.set_incident_simplex(new_incident);
}
}
}
fn incident_simplex_repair_update(
&self,
vertex_key: VertexKey,
simplices_to_remove: &RemovedSimplexMembership<'_>,
candidate_incident: &CandidateIncidentRecords,
) -> Result<Option<(VertexKey, Option<SimplexKey>)>, TdsMutationError> {
let vertex = self
.vertices
.get(vertex_key)
.ok_or_else(|| TdsError::VertexNotFound {
vertex_key,
context: "simplex-removal incident repair planning".to_string(),
})?;
let needs_repair = vertex.incident_simplex().is_none_or(|simplex_key| {
simplices_to_remove.contains(simplex_key) || !self.simplices.contains_key(simplex_key)
});
if !needs_repair {
return Ok(None);
}
let mut new_incident = candidate_incident.get(vertex_key).filter(|&simplex_key| {
self.simplices
.get(simplex_key)
.is_some_and(|simplex| simplex.contains_vertex(vertex_key))
});
if new_incident.is_none() {
new_incident =
self.valid_surviving_incident_simplex(vertex_key, simplices_to_remove)?;
}
Ok(Some((vertex_key, new_incident)))
}
fn valid_surviving_incident_simplex(
&self,
vertex_key: VertexKey,
simplices_to_remove: &RemovedSimplexMembership<'_>,
) -> Result<Option<SimplexKey>, TdsMutationError> {
if !self.vertices.contains_key(vertex_key) {
return Err(TdsError::VertexNotFound {
vertex_key,
context: "simplex-removal incident repair planning".to_string(),
}
.into());
}
let Some(simplex_key) = self.first_simplex_containing_vertex(vertex_key) else {
return Ok(None);
};
if simplices_to_remove.contains(simplex_key) {
return Err(TdsError::RemovedSimplexStillIncident {
vertex_key,
simplex_key,
}
.into());
}
let Some(simplex) = self.simplices.get(simplex_key) else {
return Err(TdsError::SimplexNotFound {
simplex_key,
context: format!("vertex-to-simplices repair planning for vertex {vertex_key:?}"),
}
.into());
};
if !simplex.contains_vertex(vertex_key) {
return Err(TdsError::VertexIncidenceMismatch {
vertex_key,
simplex_key,
}
.into());
}
Ok(Some(simplex_key))
}
pub(crate) fn remove_vertex(
&mut self,
vertex_key: VertexKey,
) -> Result<usize, TdsMutationError> {
let Some(vertex) = self.vertex(vertex_key) else {
return Ok(0); };
let uuid = vertex.uuid();
let simplices_to_remove: SimplexRemovalBuffer =
self.simplex_keys_containing_vertex(vertex_key).collect();
let simplices_removed = self.remove_simplices_by_keys(&simplices_to_remove)?;
self.journal_incidence_before_write([vertex_key]);
self.remove_vertex_incidence(vertex_key)?;
self.remove_vertex_storage_transactionally(vertex_key);
self.uuid_to_vertex_key.remove(&uuid);
self.refresh_incomplete_construction_state();
self.bump_generation();
Ok(simplices_removed)
}
pub(crate) fn remove_isolated_vertex(
&mut self,
vertex_key: VertexKey,
) -> Result<(), TdsMutationError> {
let Some(vertex) = self.vertex(vertex_key) else {
return Ok(());
};
if self
.simplex_keys_containing_vertex(vertex_key)
.next()
.is_some()
{
return Ok(());
}
let uuid = vertex.uuid();
self.journal_incidence_before_write([vertex_key]);
self.remove_vertex_incidence(vertex_key)?;
self.remove_vertex_storage_transactionally(vertex_key);
self.uuid_to_vertex_key.remove(&uuid);
self.refresh_incomplete_construction_state();
self.bump_generation();
Ok(())
}
fn validate_neighbor_update_matches_facet_incidence(
&self,
simplex_key: SimplexKey,
neighbors: &[Option<SimplexKey>],
) -> Result<(), TdsError> {
let simplex = self
.simplices
.get(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "validate_neighbor_update_matches_facet_incidence".to_string(),
})?;
let facet_to_simplices = self.build_facet_to_simplices_map()?;
for (facet_idx, proposed_neighbor) in neighbors.iter().copied().enumerate() {
let facet_key = self.facet_key_for_simplex_facet(simplex_key, facet_idx)?;
let Some(simplex_facet_pairs) = facet_to_simplices.get(&facet_key) else {
return Err(TdsError::InvalidNeighbors {
reason: NeighborValidationError::FacetIncidenceMissing {
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
facet_key,
},
});
};
let expected_neighbor = match simplex_facet_pairs.as_slice() {
[_] => None,
[a, b] => {
if a.simplex_key() == simplex_key && a.facet_index() as usize == facet_idx {
Some(b.simplex_key())
} else if b.simplex_key() == simplex_key
&& b.facet_index() as usize == facet_idx
{
Some(a.simplex_key())
} else {
return Err(TdsError::InvalidNeighbors {
reason:
NeighborValidationError::FacetIncidenceDoesNotReferenceSimplex {
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
facet_key,
},
});
}
}
_ => {
return Err(TdsError::InvalidNeighbors {
reason: NeighborValidationError::FacetIncidenceMultiplicity {
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
facet_key,
simplex_count: simplex_facet_pairs.len(),
},
});
}
};
if proposed_neighbor == Some(simplex_key)
&& expected_neighbor.is_none()
&& Self::allows_periodic_self_neighbor(simplex)
{
continue;
}
if proposed_neighbor != expected_neighbor {
return Err(TdsError::InvalidNeighbors {
reason: NeighborValidationError::NeighborIncidenceMismatch {
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
proposed_neighbor,
expected_neighbor,
},
});
}
}
Ok(())
}
fn set_simplex_neighbors_normalized(
simplex: &mut Simplex<V, D>,
neighbors: &[Option<SimplexKey>],
) -> Result<(), TdsError> {
let simplex_id = simplex.uuid();
simplex
.set_neighbors_from_keys(neighbors.iter().copied())
.map_err(|source| TdsError::InvalidSimplex { simplex_id, source })
}
fn ensure_neighbor_buffer(
simplex: &mut Simplex<V, D>,
) -> Result<&mut SmallBuffer<NeighborSlot, MAX_PRACTICAL_DIMENSION_SIZE>, TdsError> {
if simplex.neighbor_slots().is_none() {
let simplex_id = simplex.uuid();
simplex
.set_neighbors_from_keys((0..=D).map(|_| None))
.map_err(|source| TdsError::InvalidSimplex { simplex_id, source })?;
}
let simplex_id = simplex.uuid();
simplex
.try_ensure_neighbors_buffer_mut()
.map_err(|source| TdsError::InvalidSimplex { simplex_id, source })
}
fn set_neighbor_slot(
simplex: &mut Simplex<V, D>,
facet_idx: usize,
neighbor: Option<SimplexKey>,
) -> Result<(), TdsError> {
let neighbors = Self::ensure_neighbor_buffer(simplex)?;
let Some(slot) = neighbors.get_mut(facet_idx) else {
return Err(TdsError::InvalidNeighbors {
reason: NeighborValidationError::NeighborSlotOutOfBounds {
facet_index: facet_idx,
slot_count: neighbors.len(),
},
});
};
*slot = NeighborSlot::from_neighbor_key(neighbor);
Ok(())
}
fn validate_existing_neighbor_buffer_arity(
simplex: &Simplex<V, D>,
context: &str,
) -> Result<(), TdsError> {
if let Some(slots) = simplex.neighbor_slots()
&& slots.len() != D + 1
{
return Err(TdsError::InvalidNeighbors {
reason: NeighborValidationError::LengthMismatch {
actual: slots.len(),
expected: D + 1,
context: context.to_string(),
},
});
}
Ok(())
}
fn reciprocal_neighbor_updates_for_neighbor_update(
&self,
simplex_key: SimplexKey,
neighbors: &[Option<SimplexKey>],
) -> Result<Vec<(SimplexKey, usize, Option<SimplexKey>)>, TdsError> {
let simplex = self
.simplices
.get(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "set_neighbors_by_key".to_string(),
})?;
let old_neighbors: Vec<Option<SimplexKey>> = simplex
.neighbor_keys()
.map_or_else(|| vec![None; D + 1], Iterator::collect);
let mut reciprocal_updates = Vec::new();
self.collect_stale_reciprocal_neighbor_updates(
simplex_key,
simplex,
&old_neighbors,
neighbors,
&mut reciprocal_updates,
)?;
self.collect_new_reciprocal_neighbor_updates(
simplex_key,
simplex,
neighbors,
&mut reciprocal_updates,
)?;
Ok(reciprocal_updates)
}
fn collect_stale_reciprocal_neighbor_updates(
&self,
simplex_key: SimplexKey,
simplex: &Simplex<V, D>,
old_neighbors: &[Option<SimplexKey>],
new_neighbors: &[Option<SimplexKey>],
reciprocal_updates: &mut Vec<(SimplexKey, usize, Option<SimplexKey>)>,
) -> Result<(), TdsError> {
for (facet_idx, old_neighbor_key) in old_neighbors.iter().copied().enumerate() {
let Some(old_neighbor_key) = old_neighbor_key else {
continue;
};
if old_neighbor_key == simplex_key
|| new_neighbors
.iter()
.copied()
.any(|neighbor_key| neighbor_key == Some(old_neighbor_key))
{
continue;
}
let old_neighbor_simplex =
self.simplices
.get(old_neighbor_key)
.ok_or_else(|| TdsError::InvalidNeighbors {
reason: NeighborValidationError::MissingNeighborSimplex {
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
neighbor_key: old_neighbor_key,
context: "clearing stale reciprocal neighbor".to_string(),
},
})?;
let mirror_idx = simplex
.mirror_facet_index(facet_idx, old_neighbor_simplex)
.ok_or_else(|| TdsError::InvalidNeighbors {
reason: NeighborValidationError::MirrorFacetMissing {
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
neighbor_uuid: old_neighbor_simplex.uuid(),
context: "clearing old back-reference".to_string(),
},
})?;
let back_ref = old_neighbor_simplex.neighbor_key(mirror_idx).flatten();
if back_ref == Some(simplex_key) {
reciprocal_updates.push((old_neighbor_key, mirror_idx, None));
}
}
Ok(())
}
fn collect_new_reciprocal_neighbor_updates(
&self,
simplex_key: SimplexKey,
simplex: &Simplex<V, D>,
neighbors: &[Option<SimplexKey>],
reciprocal_updates: &mut Vec<(SimplexKey, usize, Option<SimplexKey>)>,
) -> Result<(), TdsError> {
for (facet_idx, neighbor_key) in neighbors.iter().copied().enumerate() {
let Some(neighbor_key) = neighbor_key else {
continue;
};
if neighbor_key == simplex_key {
continue;
}
let neighbor_simplex =
self.simplices
.get(neighbor_key)
.ok_or_else(|| TdsError::InvalidNeighbors {
reason: NeighborValidationError::MissingNeighborSimplex {
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
neighbor_key,
context: "setting reciprocal neighbor".to_string(),
},
})?;
let mirror_idx = simplex
.mirror_facet_index(facet_idx, neighbor_simplex)
.ok_or_else(|| TdsError::InvalidNeighbors {
reason: NeighborValidationError::MirrorFacetMissing {
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
neighbor_uuid: neighbor_simplex.uuid(),
context: "setting back-reference".to_string(),
},
})?;
let existing_back_ref = neighbor_simplex.neighbor_key(mirror_idx).flatten();
if let Some(existing_back_ref) = existing_back_ref
&& existing_back_ref != simplex_key
{
return Err(TdsError::InvalidNeighbors {
reason: NeighborValidationError::ExistingBackReferenceConflict {
neighbor_uuid: neighbor_simplex.uuid(),
mirror_index: mirror_idx,
existing_back_ref,
requested_back_ref: simplex_key,
},
});
}
reciprocal_updates.push((neighbor_key, mirror_idx, Some(simplex_key)));
}
Ok(())
}
pub(in crate::core) fn stage_neighbor_slot_for_topology_candidate(
&mut self,
simplex_key: SimplexKey,
facet_idx: usize,
requested_neighbor: Option<SimplexKey>,
) -> Result<(), TdsMutationError> {
let simplex = self
.simplices
.get(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "staging a topology-candidate neighbor slot".to_string(),
})?;
let simplex_uuid = simplex.uuid();
let vertex_count = simplex.number_of_vertices();
if vertex_count != D + 1 {
return Err(TdsError::DimensionMismatch {
expected: D + 1,
actual: vertex_count,
context: format!("{D}-dimensional simplex vertex count"),
}
.into());
}
Self::validate_existing_neighbor_buffer_arity(
simplex,
"staging a topology-candidate neighbor slot",
)?;
if facet_idx >= vertex_count {
return Err(TdsError::IndexOutOfBounds {
index: facet_idx,
bound: vertex_count,
context: format!("facet slot for simplex {simplex_key:?}"),
}
.into());
}
if let Some(neighbor_key) = requested_neighbor {
if neighbor_key == simplex_key && !Self::allows_periodic_self_neighbor(simplex) {
return Err(TdsError::InvalidNeighbors {
reason: NeighborValidationError::NonPeriodicSelfNeighbor {
simplex_key,
simplex_uuid,
facet_index: facet_idx,
},
}
.into());
}
let neighbor =
self.simplices
.get(neighbor_key)
.ok_or_else(|| TdsError::InvalidNeighbors {
reason: NeighborValidationError::MissingNeighborSimplex {
simplex_key,
simplex_uuid,
facet_index: facet_idx,
neighbor_key,
context: "staging a topology-candidate neighbor slot".to_string(),
},
})?;
let neighbor_vertex_count = neighbor.number_of_vertices();
if neighbor_vertex_count != D + 1 {
return Err(TdsError::DimensionMismatch {
expected: D + 1,
actual: neighbor_vertex_count,
context: format!("{D}-dimensional neighbor simplex vertex count"),
}
.into());
}
Self::validate_existing_neighbor_buffer_arity(
neighbor,
"staging a topology-candidate neighbor slot",
)?;
if simplex.mirror_facet_index(facet_idx, neighbor).is_none() {
return Err(TdsError::InvalidNeighbors {
reason: NeighborValidationError::MirrorFacetMissing {
simplex_uuid,
facet_index: facet_idx,
neighbor_uuid: neighbor.uuid(),
context: "staging a topology-candidate neighbor slot".to_string(),
},
}
.into());
}
}
self.journal_simplex_before_write(simplex_key);
let simplex = self
.simplex_mut(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "committing a topology-candidate neighbor slot".to_string(),
})?;
Self::set_neighbor_slot(simplex, facet_idx, requested_neighbor)?;
self.bump_generation();
Ok(())
}
pub fn set_neighbors_by_key(
&mut self,
simplex_key: SimplexKey,
neighbors: &[Option<SimplexKey>],
) -> Result<(), TdsMutationError> {
self.validate_neighbor_topology(simplex_key, neighbors)?;
self.validate_neighbor_update_matches_facet_incidence(simplex_key, neighbors)?;
let reciprocal_updates =
self.reciprocal_neighbor_updates_for_neighbor_update(simplex_key, neighbors)?;
self.journal_simplex_before_write(simplex_key);
let simplex_uuid = {
let simplex =
self.simplex_mut(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "set_neighbors_by_key".to_string(),
})?;
let simplex_uuid = simplex.uuid();
Self::set_simplex_neighbors_normalized(simplex, neighbors)?;
simplex_uuid
};
for (neighbor_key, mirror_idx, back_reference) in reciprocal_updates {
self.journal_simplex_before_write(neighbor_key);
let neighbor_simplex =
self.simplices
.get_mut(neighbor_key)
.ok_or_else(|| TdsError::InvalidNeighbors {
reason: NeighborValidationError::MissingNeighborSimplex {
simplex_key,
simplex_uuid,
facet_index: mirror_idx,
neighbor_key,
context: "applying reciprocal neighbor update".to_string(),
},
})?;
Self::set_neighbor_slot(neighbor_simplex, mirror_idx, back_reference)?;
}
self.bump_generation();
Ok(())
}
#[expect(
clippy::too_many_lines,
reason = "preflight and commit remain adjacent so neighbor repair cannot write before every facet check succeeds"
)]
pub(in crate::core) fn repair_neighbors_by_key_from_facet_incidence(
&mut self,
simplex_key: SimplexKey,
neighbors: &[Option<SimplexKey>],
) -> Result<(), TdsMutationError> {
self.validate_neighbor_topology(simplex_key, neighbors)?;
self.validate_neighbor_update_matches_facet_incidence(simplex_key, neighbors)?;
let simplex = self
.simplices
.get(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "repairing neighbors from facet incidence".to_string(),
})?;
let simplex_uuid = simplex.uuid();
let old_neighbors: Vec<Option<SimplexKey>> = simplex
.neighbor_keys()
.map_or_else(|| vec![None; D + 1], Iterator::collect);
let mut stale_back_references = Vec::new();
for old_neighbor_key in old_neighbors.into_iter().flatten() {
if old_neighbor_key == simplex_key || neighbors.contains(&Some(old_neighbor_key)) {
continue;
}
let Some(old_neighbor) = self.simplices.get(old_neighbor_key) else {
continue;
};
Self::validate_existing_neighbor_buffer_arity(
old_neighbor,
"checking stale reciprocal slots before neighbor repair",
)?;
if let Some(old_neighbor_keys) = old_neighbor.neighbor_keys() {
for (mirror_idx, back_reference) in old_neighbor_keys.enumerate() {
if back_reference == Some(simplex_key) {
stale_back_references.push((old_neighbor_key, mirror_idx));
}
}
}
}
let mut new_back_references = Vec::new();
for (facet_idx, neighbor_key) in neighbors.iter().copied().enumerate() {
let Some(neighbor_key) = neighbor_key else {
continue;
};
if neighbor_key == simplex_key {
continue;
}
let neighbor =
self.simplices
.get(neighbor_key)
.ok_or_else(|| TdsError::InvalidNeighbors {
reason: NeighborValidationError::MissingNeighborSimplex {
simplex_key,
simplex_uuid,
facet_index: facet_idx,
neighbor_key,
context: "checking reciprocal slots before neighbor repair".to_string(),
},
})?;
Self::validate_existing_neighbor_buffer_arity(
neighbor,
"checking reciprocal slots before neighbor repair",
)?;
let mirror_idx = simplex
.mirror_facet_index(facet_idx, neighbor)
.ok_or_else(|| TdsError::InvalidNeighbors {
reason: NeighborValidationError::MirrorFacetMissing {
simplex_uuid,
facet_index: facet_idx,
neighbor_uuid: neighbor.uuid(),
context: "checking reciprocal slots before neighbor repair".to_string(),
},
})?;
new_back_references.push((neighbor_key, mirror_idx));
}
self.journal_simplex_before_write(simplex_key);
let simplex = self
.simplex_mut(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "committing neighbor repair".to_string(),
})?;
Self::set_simplex_neighbors_normalized(simplex, neighbors)?;
for (neighbor_key, mirror_idx) in stale_back_references {
self.journal_simplex_before_write(neighbor_key);
let neighbor =
self.simplices
.get_mut(neighbor_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key: neighbor_key,
context: "committing stale reciprocal cleanup".to_string(),
})?;
Self::set_neighbor_slot(neighbor, mirror_idx, None)?;
}
for (neighbor_key, mirror_idx) in new_back_references {
self.journal_simplex_before_write(neighbor_key);
let neighbor =
self.simplices
.get_mut(neighbor_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key: neighbor_key,
context: "committing reciprocal neighbor repair".to_string(),
})?;
Self::set_neighbor_slot(neighbor, mirror_idx, Some(simplex_key))?;
}
self.bump_generation();
Ok(())
}
pub fn assign_incident_simplices(&mut self) -> Result<(), TdsMutationError> {
self.rebuild_vertex_to_simplices_index()?;
let incident_updates: Vec<_> = self
.vertices
.keys()
.map(|vertex_key| {
let incident_simplex = self.vertex_to_simplices.simplex_keys(vertex_key).next();
(vertex_key, incident_simplex)
})
.collect();
for (vertex_key, incident_simplex) in incident_updates {
self.journal_vertex_before_write(vertex_key);
if let Some(vertex) = self.vertices.get_mut(vertex_key) {
vertex.set_incident_simplex(incident_simplex);
}
}
self.bump_generation();
Ok(())
}
pub(crate) fn set_incident_simplex_hint(
&mut self,
vertex_key: VertexKey,
simplex_key: SimplexKey,
) -> Result<(), TdsError> {
if !self.vertices.contains_key(vertex_key) {
return Err(TdsError::VertexNotFound {
vertex_key,
context: "setting incident-simplex hint".to_string(),
});
}
let simplex = self
.simplices
.get(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: format!("incident-simplex hint for vertex {vertex_key:?}"),
})?;
if !simplex.contains_vertex(vertex_key)
|| !self
.vertex_to_simplices
.simplex_keys(vertex_key)
.any(|candidate| candidate == simplex_key)
{
return Err(TdsError::VertexIncidenceMismatch {
vertex_key,
simplex_key,
});
}
self.journal_vertex_before_write(vertex_key);
let vertex = self
.vertices
.get_mut(vertex_key)
.ok_or_else(|| TdsError::VertexNotFound {
vertex_key,
context: "committing incident-simplex hint".to_string(),
})?;
vertex.set_incident_simplex(Some(simplex_key));
Ok(())
}
pub(crate) fn reverse_simplex_orientations(
&mut self,
simplex_keys: &[SimplexKey],
) -> Result<(), TdsError> {
if simplex_keys.is_empty() {
return Ok(());
}
let mut targets = SimplexKeySet::default();
targets.reserve(simplex_keys.len());
for &simplex_key in simplex_keys {
if !targets.insert(simplex_key) {
continue;
}
let simplex =
self.simplices
.get(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "checking orientation-reversal target".to_string(),
})?;
let vertex_count = simplex.number_of_vertices();
if vertex_count < 2 {
return Err(TdsError::DimensionMismatch {
expected: 2,
actual: vertex_count,
context: format!(
"simplex {simplex_key:?} needs at least two vertices for orientation reversal"
),
});
}
if vertex_count != D + 1 {
return Err(TdsError::DimensionMismatch {
expected: D + 1,
actual: vertex_count,
context: format!(
"{D}-dimensional orientation-reversal target simplex vertex count"
),
});
}
Self::validate_existing_neighbor_buffer_arity(
simplex,
"checking orientation-reversal target",
)?;
if let Some(offsets) = simplex.periodic_vertex_offsets()
&& offsets.len() != vertex_count
{
return Err(TdsError::InvalidSimplex {
simplex_id: simplex.uuid(),
source: SimplexValidationError::PeriodicOffsetLengthMismatch {
expected: vertex_count,
found: offsets.len(),
},
});
}
}
for &simplex_key in &targets {
self.journal_simplex_before_write(simplex_key);
}
for (simplex_key, simplex) in self.simplices.iter_mut() {
if targets.contains(&simplex_key) {
simplex.swap_vertex_slots(0, 1);
}
}
self.bump_generation();
Ok(())
}
pub(crate) fn normalize_coherent_orientation(&mut self) -> Result<(), TdsError> {
let mut flip_assignment: FastHashMap<SimplexKey, bool> =
fast_hash_map_with_capacity(self.simplices.len());
for root_simplex_key in self.simplices.keys() {
if flip_assignment.contains_key(&root_simplex_key) {
continue;
}
flip_assignment.insert(root_simplex_key, false);
let mut queue = VecDeque::new();
queue.push_back(root_simplex_key);
while let Some(simplex_key) = queue.pop_front() {
let this_flip_state = *flip_assignment.get(&simplex_key).ok_or_else(|| {
TdsError::InconsistentDataStructure {
message: format!(
"Missing flip assignment for simplex {simplex_key:?} during orientation normalization",
),
}
})?;
let simplex =
self.simplices
.get(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "orientation normalization traversal".to_string(),
})?;
let Some(neighbors) = simplex.neighbor_keys() else {
continue;
};
for (facet_idx, neighbor_key_opt) in neighbors.enumerate() {
let Some(neighbor_key) = neighbor_key_opt else {
continue;
};
let neighbor_simplex =
self.simplices
.get(neighbor_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key: neighbor_key,
context: format!(
"neighbor of simplex {simplex_key:?} during orientation normalization"
),
})?;
let mirror_idx = Self::orientation_mirror_facet_index(
simplex,
facet_idx,
neighbor_simplex,
"orientation normalization",
)?;
let (currently_coherent, _, _) = Self::facet_permutation_parity(
simplex,
facet_idx,
neighbor_simplex,
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 simplices {:?} and {:?}",
simplex.uuid(),
neighbor_simplex.uuid(),
),
});
}
} else {
flip_assignment.insert(neighbor_key, required_neighbor_flip_state);
queue.push_back(neighbor_key);
}
}
}
}
let mut flipped_any = false;
for (simplex_key, should_flip) in flip_assignment {
if !should_flip {
continue;
}
self.journal_simplex_before_write(simplex_key);
let simplex =
self.simplices
.get_mut(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "applying orientation normalization".to_string(),
})?;
if simplex.number_of_vertices() >= 2 {
simplex.swap_vertex_slots(0, 1);
flipped_any = true;
}
}
if flipped_any {
self.bump_generation();
}
Ok(())
}
pub(crate) fn validate_coherent_orientation_for_simplices(
&self,
simplices: &[SimplexKey],
) -> Result<(), TdsError> {
for &simplex_key in simplices {
let simplex =
self.simplices
.get(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "local orientation validation scope".to_string(),
})?;
let Some(neighbors) = simplex.neighbor_keys() else {
continue;
};
for (facet_idx, neighbor_key_opt) in neighbors.enumerate() {
let Some(neighbor_key) = neighbor_key_opt else {
continue;
};
let neighbor_simplex =
self.simplices
.get(neighbor_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key: neighbor_key,
context: format!(
"neighbor of simplex {simplex_key:?} during local orientation validation",
),
})?;
let mirror_idx = Self::orientation_mirror_facet_index(
simplex,
facet_idx,
neighbor_simplex,
"local orientation validation",
)?;
let observed_back_reference = neighbor_simplex.neighbor_key(mirror_idx).flatten();
if observed_back_reference != Some(simplex_key) {
return Err(TdsError::InvalidNeighbors {
reason: NeighborValidationError::BackReferenceMismatch {
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
neighbor_key,
neighbor_uuid: neighbor_simplex.uuid(),
mirror_index: mirror_idx,
observed: observed_back_reference,
context: "local orientation validation".to_string(),
},
});
}
let simplex1_facet_vertices =
Self::facet_vertices_in_simplex_order(simplex, facet_idx)?;
let simplex2_facet_vertices =
Self::facet_vertices_in_simplex_order(neighbor_simplex, mirror_idx)?;
let (coherent, observed_odd_permutation, expected_odd_permutation) =
Self::facet_permutation_parity(
simplex,
facet_idx,
neighbor_simplex,
mirror_idx,
)?;
if !coherent {
return Err(TdsError::OrientationViolation {
simplex1_key: simplex_key,
simplex1_uuid: simplex.uuid(),
simplex2_key: neighbor_key,
simplex2_uuid: neighbor_simplex.uuid(),
simplex1_facet_index: facet_idx,
simplex2_facet_index: mirror_idx,
facet_vertices: simplex1_facet_vertices.into_iter().collect(),
simplex2_facet_vertices: simplex2_facet_vertices.into_iter().collect(),
observed_odd_permutation,
expected_odd_permutation,
});
}
}
}
Ok(())
}
pub fn remove_duplicate_simplices(&mut self) -> Result<usize, TdsMutationError>
where
U: Clone,
V: Clone,
{
let mut unique_simplices = FastHashMap::default();
let mut simplices_to_remove = SimplexRemovalBuffer::new();
for simplex_key in self.simplices.keys() {
let vertices = self.simplex_vertices(simplex_key)?;
let vertex_uuid_offsets =
self.build_periodic_vertex_uuid_offsets(simplex_key, vertices)?;
match unique_simplices.entry(vertex_uuid_offsets) {
Entry::Occupied(_) => {
simplices_to_remove.push(simplex_key);
}
Entry::Vacant(e) => {
e.insert(simplex_key);
}
}
}
let duplicate_count = simplices_to_remove.len();
if duplicate_count == 0 {
return Ok(0);
}
let mut transaction = TdsRollbackTransaction::begin(self);
let removed = transaction
.tds_mut()
.remove_simplices_by_keys(&simplices_to_remove)?;
{
let tds = transaction.tds_mut();
tds.assign_neighbors().map_err(TdsMutationError::from)?;
tds.assign_incident_simplices()?;
tds.is_valid().map_err(TdsMutationError::from)?;
}
transaction.commit();
Ok(removed)
}
}
impl<U, V, const D: usize> UnverifiedTds<U, V, D> {
pub(in crate::core::tds) fn publish(self) -> Result<Tds<U, V, D>, TdsError> {
self.publish_recoverable()
.map_err(|failure| (*failure).into_reason())
}
pub(in crate::core::tds) fn publish_recoverable(
mut self,
) -> Result<Tds<U, V, D>, Box<RefinementError<Self, TdsError>>> {
let vertex_count = self.number_of_vertices();
let simplex_count = self.number_of_simplices();
let is_empty_complex = vertex_count == 0 && simplex_count == 0;
if !is_empty_complex && simplex_count == 0 {
return Err(Box::new(RefinementError::new(
self,
TdsError::IncompleteConstruction {
dimension: D,
vertex_count,
simplex_count,
},
)));
}
if let Err(error) = self.validate() {
return Err(Box::new(RefinementError::new(self, error)));
}
self.construction_state = TriangulationConstructionState::Constructed;
Ok(self.into_verified())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::facet::facet_key_from_vertices;
use crate::core::simplex::Simplex;
use crate::core::tds::TdsBuilder;
use crate::core::tds::errors::TriangulationConstructionState;
use crate::vertex;
use slotmap::KeyData;
use std::assert_matches;
#[test]
fn publication_rejects_an_incomplete_tds() {
let mut tds: UnverifiedTds<(), (), 2> = UnverifiedTds::empty_unpublished();
tds.insert_vertex_with_mapping(vertex![0.0, 0.0].unwrap())
.unwrap();
assert_matches!(
tds.construction_state(),
TriangulationConstructionState::Incomplete(1)
);
let error = tds.publish().unwrap_err();
assert_matches!(
error,
TdsError::IncompleteConstruction {
dimension: 2,
vertex_count: 1,
simplex_count: 0,
}
);
}
impl<U, V, const D: usize> Tds<U, V, D> {
pub(crate) fn insert_simplex_bypassing_topology_checks_for_test(
&mut self,
simplex: Simplex<V, D>,
) -> Result<SimplexKey, TdsConstructionError> {
self.insert_simplex_with_mapping_impl(
simplex,
SimplexInsertionTopologyCheck::Prechecked,
)
}
#[inline]
pub(crate) fn clear_all_neighbors(&mut self) {
for simplex in self.simplices.values_mut() {
simplex.clear_neighbors();
}
self.bump_generation();
}
}
fn initial_simplex_vertices_3d() -> [Vertex<(), 3>; 4] {
[
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
]
}
fn tds_from_specs<U: Copy, V, const D: usize>(
vertices: &[Vertex<U, D>],
simplices: &[Vec<usize>],
) -> Tds<U, V, D> {
TdsBuilder::new(vertices, simplices)
.simplex_data_type::<V>()
.build()
.unwrap()
}
fn initial_simplex_tds_3d() -> Tds<(), (), 3> {
let vertices = initial_simplex_vertices_3d();
tds_from_specs(&vertices, &[vec![0, 1, 2, 3]])
}
fn two_tetrahedra_tds_3d() -> Tds<(), (), 3> {
let vertices = [
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
vertex!([0.5, 0.5, 0.5]).unwrap(),
];
tds_from_specs(&vertices, &[vec![0, 1, 2, 3], vec![4, 1, 2, 3]])
}
#[test]
fn test_assign_neighbors_errors_on_missing_vertex_key() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0]).unwrap())
.unwrap();
let 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![a, b, c], None).unwrap())
.unwrap();
let invalid_vkey = VertexKey::from(KeyData::from_ffi(u64::MAX));
tds.simplex_mut(simplex_key)
.unwrap()
.push_vertex_key(invalid_vkey);
let err = tds.assign_neighbors().unwrap_err();
assert_eq!(
err,
TdsError::VertexNotFound {
vertex_key: invalid_vkey,
context: format!(
"referenced by simplex {} (key {simplex_key:?}) at position 3",
tds.simplex(simplex_key).unwrap().uuid()
),
}
);
}
#[test]
fn test_assign_neighbors_errors_on_non_manifold_facet_sharing() {
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 v_d = tds
.insert_vertex_with_mapping(vertex!([0.0, -1.0]).unwrap())
.unwrap();
let v_e = tds
.insert_vertex_with_mapping(vertex!([2.0, 0.0]).unwrap())
.unwrap();
let first = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(),
)
.unwrap();
let second = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v_a, v_b, v_d], None).unwrap(),
)
.unwrap();
let candidate = tds
.insert_simplex_bypassing_topology_checks_for_test(
Simplex::try_new_with_data(vec![v_a, v_b, v_e], None).unwrap(),
)
.unwrap();
let err = tds.assign_neighbors().unwrap_err();
let candidate_simplex = tds.simplex(candidate).unwrap();
let candidate_facet_index = candidate_simplex
.vertices()
.iter()
.position(|&vertex_key| vertex_key == v_e)
.unwrap();
let facet_key = facet_key_from_vertices(&[v_a, v_b]);
assert_eq!(
err,
TdsError::FacetSharingViolation {
facet_key,
existing_incident_count: 2,
attempted_incident_count: 3,
max_incident_count: 2,
candidate_simplex_uuid: candidate_simplex.uuid(),
candidate_facet_index,
}
);
assert!(tds.contains_simplex(first));
assert!(tds.contains_simplex(second));
}
#[test]
fn test_remove_simplices_by_keys_empty_is_noop() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let gen_before = tds.generation();
assert_eq!(tds.remove_simplices_by_keys(&[]).unwrap(), 0);
assert_eq!(tds.generation(), gen_before);
}
#[test]
fn test_remove_simplices_by_keys_rolls_back_incidence_order_exactly_on_batch_failure() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let shared_vertex = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
let kept_vertex_1 = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0]).unwrap())
.unwrap();
let kept_vertex_2 = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0]).unwrap())
.unwrap();
let first_removed_vertex_1 = tds
.insert_vertex_with_mapping(vertex!([2.0, 0.0]).unwrap())
.unwrap();
let first_removed_vertex_2 = tds
.insert_vertex_with_mapping(vertex!([2.0, 1.0]).unwrap())
.unwrap();
let corrupted_vertex = tds
.insert_vertex_with_mapping(vertex!([3.0, 0.0]).unwrap())
.unwrap();
let second_removed_vertex = tds
.insert_vertex_with_mapping(vertex!([3.0, 1.0]).unwrap())
.unwrap();
let kept = tds
.insert_simplex_bypassing_topology_checks_for_test(
Simplex::try_new_with_data(vec![shared_vertex, kept_vertex_1, kept_vertex_2], None)
.unwrap(),
)
.unwrap();
let first_removed = tds
.insert_simplex_bypassing_topology_checks_for_test(
Simplex::try_new_with_data(
vec![
shared_vertex,
first_removed_vertex_1,
first_removed_vertex_2,
],
None,
)
.unwrap(),
)
.unwrap();
let second_removed = tds
.insert_simplex_bypassing_topology_checks_for_test(
Simplex::try_new_with_data(
vec![shared_vertex, corrupted_vertex, second_removed_vertex],
None,
)
.unwrap(),
)
.unwrap();
let before = tds
.vertex_to_simplices_index()
.simplex_keys(shared_vertex)
.collect::<Vec<_>>();
let generation_before = tds.generation();
assert_eq!(before, vec![kept, first_removed, second_removed]);
tds.clear_vertex_incidence_for_test(corrupted_vertex);
let result = tds.remove_simplices_by_keys(&[first_removed, second_removed]);
assert!(result.is_err());
assert!(tds.contains_simplex(first_removed));
assert!(tds.contains_simplex(second_removed));
assert_eq!(tds.generation(), generation_before);
assert_eq!(
tds.vertex_to_simplices_index()
.simplex_keys(shared_vertex)
.collect::<Vec<_>>(),
before
);
}
#[test]
fn test_remove_simplices_by_keys_clears_neighbor_pointers() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0]).unwrap())
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0]).unwrap())
.unwrap();
let d = tds
.insert_vertex_with_mapping(vertex!([1.0, 1.0]).unwrap())
.unwrap();
let simplex1 = tds
.insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap())
.unwrap();
let simplex2 = tds
.insert_simplex_with_mapping(Simplex::try_new_with_data(vec![b, d, c], None).unwrap())
.unwrap();
tds.assign_neighbors().unwrap();
assert!(
tds.simplex(simplex1)
.unwrap()
.neighbors()
.is_some_and(|mut n| n.any(|neighbor| neighbor.is_some()))
);
let gen_before = tds.generation();
assert_eq!(tds.remove_simplices_by_keys(&[simplex2]).unwrap(), 1);
assert_eq!(tds.generation(), gen_before + 1);
for (_, simplex) in tds.simplices() {
if let Some(neighbors) = simplex.neighbors() {
for neighbor_opt in neighbors {
assert_ne!(neighbor_opt, Some(simplex2));
}
}
}
}
#[test]
fn test_remove_simplices_by_keys_errors_before_mutation_on_stale_incidence() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0]).unwrap())
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0]).unwrap())
.unwrap();
let d = tds
.insert_vertex_with_mapping(vertex!([1.0, 1.0]).unwrap())
.unwrap();
let stale = tds
.insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap())
.unwrap();
let requested = tds
.insert_simplex_with_mapping(Simplex::try_new_with_data(vec![b, d, c], None).unwrap())
.unwrap();
let incidence_before = tds
.vertex_to_simplices_index()
.simplex_keys(b)
.collect::<Vec<_>>();
let generation_before = tds.generation();
tds.remove_simplex_storage_only_for_test(stale);
let err = tds.remove_simplices_by_keys(&[requested]).unwrap_err();
assert_matches!(err.as_tds_error(), TdsError::SimplexNotFound { .. });
assert!(tds.contains_simplex(requested));
assert_eq!(tds.generation(), generation_before);
assert_eq!(
tds.vertex_to_simplices_index()
.simplex_keys(b)
.collect::<Vec<_>>(),
incidence_before
);
}
#[test]
fn test_remove_simplices_by_keys_repairs_incident_simplices_for_affected_vertices() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0]).unwrap())
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0]).unwrap())
.unwrap();
let d = tds
.insert_vertex_with_mapping(vertex!([1.0, 1.0]).unwrap())
.unwrap();
let simplex1 = tds
.insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap())
.unwrap();
let simplex2 = tds
.insert_simplex_with_mapping(Simplex::try_new_with_data(vec![b, d, c], None).unwrap())
.unwrap();
tds.assign_neighbors().unwrap();
tds.vertex_mut_for_test(a)
.unwrap()
.set_incident_simplex(Some(simplex1));
tds.vertex_mut_for_test(b)
.unwrap()
.set_incident_simplex(Some(simplex1));
tds.vertex_mut_for_test(c)
.unwrap()
.set_incident_simplex(Some(simplex1));
tds.vertex_mut_for_test(d)
.unwrap()
.set_incident_simplex(Some(simplex2));
assert_eq!(tds.remove_simplices_by_keys(&[simplex1]).unwrap(), 1);
assert!(tds.vertex(a).unwrap().incident_simplex().is_none());
for vk in [b, c, d] {
let incident = tds
.vertex(vk)
.unwrap()
.incident_simplex()
.expect("vertex should have an incident simplex after repair");
assert!(tds.simplices.contains_key(incident));
let simplex = tds.simplex(incident).unwrap();
assert!(simplex.contains_vertex(vk));
}
assert!(tds.is_valid().is_ok());
let simplex2_ref = tds.simplex(simplex2).unwrap();
if let Some(mut neighbors) = simplex2_ref.neighbors() {
assert!(neighbors.all(|n| n != Some(simplex1)));
}
}
#[test]
fn test_tds_remove_vertex_returns_zero_when_vertex_not_in_mapping() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let missing_key = VertexKey::from(KeyData::from_ffi(u64::MAX));
assert_eq!(tds.remove_vertex(missing_key).unwrap(), 0);
}
#[test]
fn test_remove_isolated_vertex_noop_on_missing_key() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let missing = VertexKey::from(KeyData::from_ffi(u64::MAX));
let gen_before = tds.generation();
tds.remove_isolated_vertex(missing).unwrap();
assert_eq!(tds.generation(), gen_before, "No mutation expected");
}
#[test]
fn test_remove_isolated_vertex_uses_canonical_incidence_not_stale_hint() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let vk = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
let fake_simplex_key = SimplexKey::from(KeyData::from_ffi(1));
tds.vertex_mut_for_test(vk)
.unwrap()
.set_incident_simplex(Some(fake_simplex_key));
let gen_before = tds.generation();
tds.remove_isolated_vertex(vk).unwrap();
assert_eq!(tds.generation(), gen_before + 1);
assert!(tds.vertex(vk).is_none());
assert!(!tds.vertex_to_simplices_index().contains_vertex(vk));
}
#[test]
fn test_remove_isolated_vertex_removes_truly_isolated() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let vk = tds
.insert_vertex_with_mapping(vertex!([1.0, 2.0]).unwrap())
.unwrap();
let uuid = tds.vertex(vk).unwrap().uuid();
assert!(tds.vertex(vk).unwrap().incident_simplex().is_none());
let gen_before = tds.generation();
tds.remove_isolated_vertex(vk).unwrap();
assert!(tds.generation() > gen_before);
assert!(tds.vertex(vk).is_none(), "Vertex should be removed");
assert!(
tds.vertex_key_from_uuid(&uuid).is_none(),
"UUID mapping should be removed"
);
}
#[test]
fn test_tds_remove_vertex_repairs_neighbors_and_incident_simplices_incrementally() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let origin_key = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
let east_key = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0]).unwrap())
.unwrap();
let north_key = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0]).unwrap())
.unwrap();
let diagonal_key = tds
.insert_vertex_with_mapping(vertex!([1.0, 1.0]).unwrap())
.unwrap();
let simplex1 = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![origin_key, east_key, north_key], None).unwrap(),
)
.unwrap();
let simplex2 = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![east_key, diagonal_key, north_key], None).unwrap(),
)
.unwrap();
tds.assign_neighbors().unwrap();
tds.vertex_mut_for_test(origin_key)
.unwrap()
.set_incident_simplex(Some(simplex1));
tds.vertex_mut_for_test(east_key)
.unwrap()
.set_incident_simplex(Some(simplex1));
tds.vertex_mut_for_test(north_key)
.unwrap()
.set_incident_simplex(Some(simplex1));
tds.vertex_mut_for_test(diagonal_key)
.unwrap()
.set_incident_simplex(Some(simplex2));
let origin_uuid = tds.vertex(origin_key).unwrap().uuid();
let removed = tds.remove_vertex(origin_key).unwrap();
assert_eq!(removed, 1);
assert!(tds.vertex_key_from_uuid(&origin_uuid).is_none());
assert!(tds.vertex(origin_key).is_none());
assert!(tds.simplices.contains_key(simplex2));
let simplex2_ref = tds.simplex(simplex2).unwrap();
if let Some(mut neighbors) = simplex2_ref.neighbors() {
assert!(neighbors.all(|n| n != Some(simplex1)));
}
for vertex_key in [east_key, north_key, diagonal_key] {
let v = tds.vertex(vertex_key).unwrap();
let Some(incident) = v.incident_simplex() else {
panic!("vertex {vertex_key:?} should have an incident simplex after removal");
};
assert!(tds.simplices.contains_key(incident));
assert!(tds.simplex(incident).unwrap().contains_vertex(vertex_key));
}
}
#[test]
fn test_set_neighbors_by_key_rejects_non_neighbor_and_wrong_slot() {
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 v_d = tds
.insert_vertex_with_mapping(vertex!([1.0, 1.0]).unwrap())
.unwrap();
let v_e = tds
.insert_vertex_with_mapping(vertex!([2.0, 2.0]).unwrap())
.unwrap();
let simplex1 = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(),
)
.unwrap();
let simplex_far = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v_a, v_d, v_e], None).unwrap(),
)
.unwrap();
let err = tds
.set_neighbors_by_key(simplex1, &[Some(simplex_far), None, None])
.unwrap_err()
.into_inner();
assert_matches!(err, TdsError::InvalidNeighbors { .. });
let simplex2 = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v_b, v_a, v_d], None).unwrap(),
)
.unwrap();
let err = tds
.set_neighbors_by_key(simplex1, &[Some(simplex2), None, None])
.unwrap_err()
.into_inner();
assert_matches!(err, TdsError::InvalidNeighbors { .. });
}
struct StageNeighborSlotFixture<const D: usize> {
tds: Tds<(), (), D>,
simplex_only_vertex: VertexKey,
neighbor_only_vertex: VertexKey,
simplex_key: SimplexKey,
neighbor_key: SimplexKey,
}
fn stage_neighbor_slot_fixture<const D: usize>() -> StageNeighborSlotFixture<D> {
assert!((2..=5).contains(&D));
let mut tds: Tds<(), (), D> = Tds::empty();
let mut shared_vertices = Vec::with_capacity(D);
for shared_index in 0..D {
let mut coordinates = [0.0; D];
if shared_index > 0 {
coordinates[shared_index - 1] = 1.0;
}
shared_vertices.push(
tds.insert_vertex_with_mapping(vertex!(coordinates).unwrap())
.unwrap(),
);
}
let mut simplex_only_coordinates = [0.0; D];
simplex_only_coordinates[D - 1] = 1.0;
let simplex_only_vertex = tds
.insert_vertex_with_mapping(vertex!(simplex_only_coordinates).unwrap())
.unwrap();
let mut neighbor_only_coordinates = [0.0; D];
neighbor_only_coordinates[D - 1] = -1.0;
let neighbor_only_vertex = tds
.insert_vertex_with_mapping(vertex!(neighbor_only_coordinates).unwrap())
.unwrap();
let mut simplex_vertices = shared_vertices.clone();
simplex_vertices.push(simplex_only_vertex);
let simplex_key = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(simplex_vertices, None).unwrap(),
)
.unwrap();
shared_vertices.reverse();
shared_vertices.push(neighbor_only_vertex);
let neighbor_key = tds
.insert_simplex_with_mapping(Simplex::try_new_with_data(shared_vertices, None).unwrap())
.unwrap();
StageNeighborSlotFixture {
tds,
simplex_only_vertex,
neighbor_only_vertex,
simplex_key,
neighbor_key,
}
}
fn assert_reverse_simplex_orientations_retains_nested_rollback<const D: usize>() {
let StageNeighborSlotFixture {
mut tds,
simplex_key,
neighbor_key,
..
} = stage_neighbor_slot_fixture::<D>();
tds.assign_neighbors().unwrap();
tds.assign_incident_simplices().unwrap();
tds.normalize_coherent_orientation().unwrap();
tds.force_construction_complete_for_test();
tds.validate().unwrap();
let before = serde_json::to_value(&tds).unwrap();
let owner = tds.topology_owner_id();
let generation = tds.generation();
let vertex_keys: Vec<_> = tds.vertex_keys().collect();
let simplex_keys: Vec<_> = tds.simplex_keys().collect();
let simplex_vertices = tds.simplex(simplex_key).unwrap().vertices().to_vec();
let outer = tds.begin_rollback_savepoint();
tds.reverse_simplex_orientations(&[simplex_key, simplex_key])
.unwrap();
assert_eq!(tds.generation(), generation + 1);
let reversed = tds.simplex(simplex_key).unwrap().vertices();
assert_eq!(reversed[0], simplex_vertices[1]);
assert_eq!(reversed[1], simplex_vertices[0]);
assert_eq!(reversed[2..], simplex_vertices[2..]);
let inner = tds.begin_rollback_savepoint();
tds.reverse_simplex_orientations(&[neighbor_key]).unwrap();
tds.commit_savepoint(inner);
assert_eq!(tds.generation(), generation + 2);
tds.validate().unwrap();
tds.rollback_savepoint(outer);
assert_eq!(tds.topology_owner_id(), owner);
assert_eq!(tds.generation(), generation);
assert_eq!(tds.vertex_keys().collect::<Vec<_>>(), vertex_keys);
assert_eq!(tds.simplex_keys().collect::<Vec<_>>(), simplex_keys);
assert_eq!(serde_json::to_value(&tds).unwrap(), before);
tds.validate().unwrap();
}
fn assert_reverse_simplex_orientations_rejects_malformed_neighbors_without_mutation<
const D: usize,
>() {
let StageNeighborSlotFixture {
mut tds,
simplex_key,
neighbor_key,
..
} = stage_neighbor_slot_fixture::<D>();
let neighbor = tds.simplex_mut(neighbor_key).unwrap();
neighbor
.set_neighbors_from_keys(vec![None; D + 1])
.expect("test neighbor buffer should match simplex arity");
neighbor.neighbor_slots_mut().unwrap().truncate(D);
let simplex_vertices_before = tds.simplex(simplex_key).unwrap().vertices().to_vec();
let neighbor_vertices_before = tds.simplex(neighbor_key).unwrap().vertices().to_vec();
let neighbor_slots_before = tds
.simplex(neighbor_key)
.unwrap()
.neighbor_slots()
.unwrap()
.clone();
let generation = tds.generation();
let error = tds
.reverse_simplex_orientations(&[simplex_key, neighbor_key])
.unwrap_err();
assert_matches!(
error,
TdsError::InvalidNeighbors {
reason: NeighborValidationError::LengthMismatch {
actual,
expected,
..
}
} if actual == D && expected == D + 1
);
assert_eq!(
tds.simplex(simplex_key).unwrap().vertices(),
simplex_vertices_before
);
assert_eq!(
tds.simplex(neighbor_key).unwrap().vertices(),
neighbor_vertices_before
);
assert_eq!(
tds.simplex(neighbor_key).unwrap().neighbor_slots(),
Some(&neighbor_slots_before)
);
assert_eq!(tds.generation(), generation);
}
fn assert_reverse_simplex_orientations_rejects_misaligned_offsets_without_mutation<
const D: usize,
>() {
let StageNeighborSlotFixture {
mut tds,
simplex_key,
neighbor_key,
..
} = stage_neighbor_slot_fixture::<D>();
tds.simplex_mut(neighbor_key)
.unwrap()
.periodic_vertex_offsets = Some(vec![[0_i8; D]; D].into());
let simplex_vertices_before = tds.simplex(simplex_key).unwrap().vertices().to_vec();
let neighbor_vertices_before = tds.simplex(neighbor_key).unwrap().vertices().to_vec();
let offsets_before = tds
.simplex(neighbor_key)
.unwrap()
.periodic_vertex_offsets()
.unwrap()
.to_vec();
let generation = tds.generation();
let error = tds
.reverse_simplex_orientations(&[simplex_key, neighbor_key])
.unwrap_err();
assert_matches!(
error,
TdsError::InvalidSimplex {
source: SimplexValidationError::PeriodicOffsetLengthMismatch {
expected,
found,
},
..
} if expected == D + 1 && found == D
);
assert_eq!(
tds.simplex(simplex_key).unwrap().vertices(),
simplex_vertices_before
);
assert_eq!(
tds.simplex(neighbor_key).unwrap().vertices(),
neighbor_vertices_before
);
assert_eq!(
tds.simplex(neighbor_key).unwrap().periodic_vertex_offsets(),
Some(offsets_before.as_slice())
);
assert_eq!(tds.generation(), generation);
}
fn assert_stage_neighbor_slot_rejects_invalid_candidates_without_mutation<const D: usize>() {
let StageNeighborSlotFixture {
mut tds,
simplex_key,
neighbor_key,
..
} = stage_neighbor_slot_fixture::<D>();
let generation = tds.generation();
let missing_key = SimplexKey::from(KeyData::from_ffi(0xDEAD));
let error = tds
.stage_neighbor_slot_for_topology_candidate(missing_key, 0, None)
.unwrap_err()
.into_inner();
assert_matches!(
error,
TdsError::SimplexNotFound { simplex_key, .. } if simplex_key == missing_key
);
let error = tds
.stage_neighbor_slot_for_topology_candidate(simplex_key, D + 1, None)
.unwrap_err()
.into_inner();
assert_matches!(
error,
TdsError::IndexOutOfBounds {
index,
bound,
..
} if index == D + 1 && bound == D + 1
);
let error = tds
.stage_neighbor_slot_for_topology_candidate(simplex_key, 0, Some(simplex_key))
.unwrap_err()
.into_inner();
assert_matches!(
error,
TdsError::InvalidNeighbors {
reason: NeighborValidationError::NonPeriodicSelfNeighbor {
simplex_key: rejected,
facet_index: 0,
..
}
} if rejected == simplex_key
);
let error = tds
.stage_neighbor_slot_for_topology_candidate(simplex_key, 0, Some(missing_key))
.unwrap_err()
.into_inner();
assert_matches!(
error,
TdsError::InvalidNeighbors {
reason: NeighborValidationError::MissingNeighborSimplex {
neighbor_key: missing,
facet_index: 0,
..
}
} if missing == missing_key
);
let error = tds
.stage_neighbor_slot_for_topology_candidate(simplex_key, 0, Some(neighbor_key))
.unwrap_err()
.into_inner();
assert_matches!(
error,
TdsError::InvalidNeighbors {
reason: NeighborValidationError::MirrorFacetMissing { facet_index: 0, .. }
}
);
assert_eq!(tds.generation(), generation);
}
fn assert_stage_neighbor_slot_rejects_malformed_simplices_and_buffers_without_mutation<
const D: usize,
>() {
let StageNeighborSlotFixture {
mut tds,
simplex_only_vertex,
neighbor_only_vertex,
simplex_key,
neighbor_key,
} = stage_neighbor_slot_fixture::<D>();
let generation = tds.generation();
let simplex = tds.simplex_mut(simplex_key).unwrap();
simplex
.set_neighbors_from_keys(vec![None; D + 1])
.expect("test neighbor buffer should match simplex arity");
simplex.neighbor_slots_mut().unwrap().truncate(D);
let error = tds
.stage_neighbor_slot_for_topology_candidate(simplex_key, D, Some(neighbor_key))
.unwrap_err()
.into_inner();
assert_matches!(
error,
TdsError::InvalidNeighbors {
reason: NeighborValidationError::LengthMismatch {
actual,
expected,
..
}
} if actual == D && expected == D + 1
);
tds.simplex_mut(simplex_key)
.unwrap()
.set_neighbors_from_keys(vec![None; D + 1])
.expect("test neighbor buffer should match simplex arity");
let neighbor = tds.simplex_mut(neighbor_key).unwrap();
neighbor
.set_neighbors_from_keys(vec![None; D + 1])
.expect("test neighbor buffer should match simplex arity");
neighbor.neighbor_slots_mut().unwrap().truncate(D);
let error = tds
.stage_neighbor_slot_for_topology_candidate(simplex_key, D, Some(neighbor_key))
.unwrap_err()
.into_inner();
assert_matches!(
error,
TdsError::InvalidNeighbors {
reason: NeighborValidationError::LengthMismatch {
actual,
expected,
..
}
} if actual == D && expected == D + 1
);
tds.simplex_mut(neighbor_key)
.unwrap()
.set_neighbors_from_keys(vec![None; D + 1])
.expect("test neighbor buffer should match simplex arity");
tds.simplex_mut(neighbor_key)
.unwrap()
.push_vertex_key(simplex_only_vertex);
let error = tds
.stage_neighbor_slot_for_topology_candidate(simplex_key, D, Some(neighbor_key))
.unwrap_err()
.into_inner();
assert_matches!(
error,
TdsError::DimensionMismatch {
expected,
actual,
..
} if expected == D + 1 && actual == D + 2
);
tds.push_first_simplex_vertex_key_storage_only_for_test(neighbor_only_vertex);
assert_eq!(
tds.simplex(simplex_key).unwrap().number_of_vertices(),
D + 2
);
let error = tds
.stage_neighbor_slot_for_topology_candidate(simplex_key, D, None)
.unwrap_err()
.into_inner();
assert_matches!(
error,
TdsError::DimensionMismatch {
expected,
actual,
..
} if expected == D + 1 && actual == D + 2
);
assert_eq!(tds.generation(), generation);
}
macro_rules! generate_stage_neighbor_slot_tests {
($($dim:expr),+ $(,)?) => {
pastey::paste! {
$(
#[test]
fn [<reverse_simplex_orientations_retains_nested_rollback_ $dim d>]() {
assert_reverse_simplex_orientations_retains_nested_rollback::<$dim>();
}
#[test]
fn [<reverse_simplex_orientations_rejects_malformed_neighbors_without_mutation_ $dim d>]() {
assert_reverse_simplex_orientations_rejects_malformed_neighbors_without_mutation::<$dim>();
}
#[test]
fn [<reverse_simplex_orientations_rejects_misaligned_offsets_without_mutation_ $dim d>]() {
assert_reverse_simplex_orientations_rejects_misaligned_offsets_without_mutation::<$dim>();
}
#[test]
fn [<stage_neighbor_slot_rejects_invalid_candidates_without_mutation_ $dim d>]() {
assert_stage_neighbor_slot_rejects_invalid_candidates_without_mutation::<$dim>();
}
#[test]
fn [<stage_neighbor_slot_rejects_malformed_simplices_and_buffers_without_mutation_ $dim d>]() {
assert_stage_neighbor_slot_rejects_malformed_simplices_and_buffers_without_mutation::<$dim>();
}
)+
}
};
}
generate_stage_neighbor_slot_tests!(2, 3, 4, 5);
#[test]
fn test_set_neighbors_by_key_updates_reciprocal_back_reference() {
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 v_d = tds
.insert_vertex_with_mapping(vertex!([1.0, 1.0]).unwrap())
.unwrap();
let simplex1 = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(),
)
.unwrap();
let simplex2 = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v_b, v_a, v_d], None).unwrap(),
)
.unwrap();
tds.set_neighbors_by_key(simplex1, &[None, None, Some(simplex2)])
.unwrap();
assert_eq!(
tds.simplex(simplex1).unwrap().neighbor_key(2).flatten(),
Some(simplex2)
);
assert_eq!(
tds.simplex(simplex2).unwrap().neighbor_key(2).flatten(),
Some(simplex1)
);
tds.is_valid().unwrap();
}
#[test]
fn test_set_neighbors_by_key_rejects_unpaired_interior_facet() {
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 v_d = tds
.insert_vertex_with_mapping(vertex!([1.0, 1.0]).unwrap())
.unwrap();
let simplex1 = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v_a, v_b, v_c], None).unwrap(),
)
.unwrap();
tds.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v_b, v_a, v_d], None).unwrap(),
)
.unwrap();
tds.assign_neighbors().unwrap();
let err = tds
.set_neighbors_by_key(simplex1, &[None, None, None])
.unwrap_err()
.into_inner();
assert_matches!(err, TdsError::InvalidNeighbors { .. });
assert!(tds.is_valid().is_ok());
}
macro_rules! test_normalize_repairs_incoherent_adjacent_pair {
($name:ident, $dim:literal) => {
#[test]
fn $name() {
let mut tds: Tds<(), (), $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())
.unwrap(),
);
}
let simplex1_vertices: Vec<_> =
vertex_keys.iter().take($dim + 1).copied().collect();
let mut simplex2_vertices: Vec<_> =
vertex_keys.iter().take($dim).copied().collect();
simplex2_vertices.push(vertex_keys[$dim + 1]);
let simplex1: Simplex<(), $dim> =
Simplex::try_new_with_data(simplex1_vertices, None).unwrap();
let simplex2: Simplex<(), $dim> =
Simplex::try_new_with_data(simplex2_vertices, None).unwrap();
tds.insert_simplex_with_mapping(simplex1).unwrap();
tds.insert_simplex_with_mapping(simplex2).unwrap();
tds.assign_neighbors().unwrap();
assert!(!tds.is_coherently_oriented());
tds.normalize_coherent_orientation().unwrap();
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_simplices_clears_incident_simplex_when_no_simplices() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let vkey = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
tds.vertex_mut_for_test(vkey)
.unwrap()
.set_incident_simplex(Some(SimplexKey::from(KeyData::from_ffi(u64::MAX))));
assert!(tds.vertex(vkey).unwrap().incident_simplex().is_some());
tds.assign_incident_simplices().unwrap();
assert!(tds.vertex(vkey).unwrap().incident_simplex().is_none());
}
#[test]
fn set_incident_simplex_hint_accepts_only_canonical_incidence() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0]).unwrap())
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0]).unwrap())
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0]).unwrap())
.unwrap();
let isolated = tds
.insert_vertex_with_mapping(vertex!([2.0, 2.0]).unwrap())
.unwrap();
let simplex = tds
.insert_simplex_with_mapping(Simplex::try_new_with_data(vec![a, b, c], None).unwrap())
.unwrap();
tds.set_incident_simplex_hint(a, simplex).unwrap();
assert_eq!(tds.vertex(a).unwrap().incident_simplex(), Some(simplex));
let error = tds
.set_incident_simplex_hint(isolated, simplex)
.unwrap_err();
assert_matches!(
error,
TdsError::VertexIncidenceMismatch {
vertex_key,
simplex_key,
} if vertex_key == isolated && simplex_key == simplex
);
assert!(tds.vertex(isolated).unwrap().incident_simplex().is_none());
tds.validate().unwrap();
}
#[test]
fn test_normalize_coherent_orientation_produces_coherent_result() {
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 v3 = tds
.insert_vertex_with_mapping(vertex!([1.0, 1.0]).unwrap())
.unwrap();
let c0 = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.unwrap();
let c1 = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v1, v2, v3], None).unwrap(),
)
.unwrap();
tds.simplex_mut(c0)
.unwrap()
.set_neighbors_from_keys([Some(c1), None, None])
.unwrap();
tds.simplex_mut(c1)
.unwrap()
.set_neighbors_from_keys([None, None, Some(c0)])
.unwrap();
tds.normalize_coherent_orientation().unwrap();
assert!(tds.is_coherently_oriented());
}
#[test]
fn test_remove_duplicate_simplices_removes_exact_duplicates() {
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();
tds.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.unwrap();
tds.insert_simplex_bypassing_topology_checks_for_test(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.unwrap();
assert_eq!(tds.number_of_simplices(), 2);
let generation_before = tds.generation();
let removed = tds.remove_duplicate_simplices().unwrap();
assert_eq!(removed, 1);
assert_eq!(tds.number_of_simplices(), 1);
assert!(tds.generation() > generation_before);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_remove_duplicate_simplices_noop_when_no_duplicates() {
let mut tds = initial_simplex_tds_3d();
let generation_before = tds.generation();
let removed = tds.remove_duplicate_simplices().unwrap();
assert_eq!(removed, 0);
assert_eq!(tds.generation(), generation_before);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_remove_duplicate_simplices_rolls_back_when_rebuild_fails() {
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 v3 = tds
.insert_vertex_with_mapping(vertex!([0.0, -1.0]).unwrap())
.unwrap();
let v4 = tds
.insert_vertex_with_mapping(vertex!([0.5, -0.5]).unwrap())
.unwrap();
tds.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.unwrap();
tds.insert_simplex_bypassing_topology_checks_for_test(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.unwrap();
tds.insert_simplex_bypassing_topology_checks_for_test(
Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(),
)
.unwrap();
let oversharing_candidate_key = tds
.insert_simplex_bypassing_topology_checks_for_test(
Simplex::try_new_with_data(vec![v0, v1, v4], None).unwrap(),
)
.unwrap();
let oversharing_candidate = tds.simplex(oversharing_candidate_key).unwrap();
let expected_candidate_uuid = oversharing_candidate.uuid();
let expected_facet_key = Tds::<(), (), 2>::periodic_facet_key_from_simplex_vertices(
oversharing_candidate,
oversharing_candidate.vertices(),
2,
)
.unwrap();
let before = tds.clone();
let generation_before = tds.generation();
let error = tds.remove_duplicate_simplices().unwrap_err();
assert_eq!(
error.into_inner(),
TdsError::FacetSharingViolation {
facet_key: expected_facet_key,
existing_incident_count: 2,
attempted_incident_count: 3,
max_incident_count: 2,
candidate_simplex_uuid: expected_candidate_uuid,
candidate_facet_index: 2,
}
);
assert_eq!(tds.number_of_simplices(), 4);
assert_eq!(tds.generation(), generation_before);
assert_eq!(tds, before);
}
#[test]
fn test_clear_all_neighbors_and_rebuild() {
let mut tds = two_tetrahedra_tds_3d();
assert!(tds.number_of_simplices() > 1);
let has_any_neighbor = tds.simplices().any(|(_, simplex)| {
simplex
.neighbors()
.is_some_and(|mut nb| nb.any(|neighbor| neighbor.is_some()))
});
assert!(has_any_neighbor);
tds.clear_all_neighbors();
for (_, simplex) in tds.simplices() {
assert!(simplex.neighbors().is_none());
}
}
#[test]
fn test_insert_simplex_with_mapping_registers_uuid_mapping() {
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 simplex = Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap();
let simplex_uuid = simplex.uuid();
let ck = tds.insert_simplex_with_mapping(simplex).unwrap();
assert_eq!(tds.simplex_key_from_uuid(&simplex_uuid), Some(ck));
assert_eq!(tds.number_of_simplices(), 1);
}
#[test]
fn test_insert_simplex_with_mapping_rejects_missing_vertex() {
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 stale = VertexKey::from(KeyData::from_ffi(0xDEAD));
let simplex = Simplex::try_new_with_data(vec![v0, v1, stale], None).unwrap();
let err = tds.insert_simplex_with_mapping(simplex).unwrap_err();
assert_matches!(
err,
TdsConstructionError::ValidationError {
source: TdsError::VertexNotFound { .. }
}
);
}
#[test]
fn test_insert_simplex_with_mapping_trusted_vertices_rejects_missing_vertex() {
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 stale = VertexKey::from(KeyData::from_ffi(0xDEAD));
let simplex = Simplex::try_new_with_data(vec![v0, v1, stale], None).unwrap();
let err = tds
.insert_simplex_with_mapping_trusted_vertices(simplex)
.unwrap_err();
assert_matches!(
err,
TdsConstructionError::ValidationError {
source: TdsError::VertexNotFound { .. }
}
);
}
#[test]
fn test_insert_simplex_with_mapping_rejects_duplicate_uuid() {
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 simplex_a = Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap();
let uuid_a = simplex_a.uuid();
tds.insert_simplex_with_mapping(simplex_a).unwrap();
let simplex_b = Simplex::try_new_with_uuid(vec![v0, v1, v2], uuid_a, None).unwrap();
let err = tds.insert_simplex_with_mapping(simplex_b).unwrap_err();
assert_matches!(err, TdsConstructionError::DuplicateUuid { .. });
}
#[test]
fn test_insert_simplex_rejects_candidate_periodic_offset_count_mismatch() {
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 mut candidate = Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap();
let candidate_uuid = candidate.uuid();
let generation_before = tds.generation();
candidate.periodic_vertex_offsets = Some(vec![[0_i8, 0_i8], [0_i8, 0_i8]].into());
let err = tds.insert_simplex_with_mapping(candidate).unwrap_err();
assert_matches!(
err,
TdsConstructionError::ValidationError {
source: TdsError::DimensionMismatch {
expected: 3,
actual: 2,
..
}
}
);
assert_eq!(tds.number_of_simplices(), 0);
assert_eq!(tds.generation(), generation_before);
assert_eq!(tds.simplex_key_from_uuid(&candidate_uuid), None);
}
#[test]
fn test_insert_simplex_propagates_existing_periodic_facet_key_error() {
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 v3 = tds
.insert_vertex_with_mapping(vertex!([1.0, 1.0]).unwrap())
.unwrap();
let existing = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.unwrap();
tds.simplex_mut(existing)
.unwrap()
.set_periodic_vertex_offsets(vec![[-128_i8, 0_i8], [127_i8, 0_i8], [0_i8, 0_i8]])
.unwrap();
let candidate = Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap();
let candidate_uuid = candidate.uuid();
let generation_before = tds.generation();
let err = tds.insert_simplex_with_mapping(candidate).unwrap_err();
assert_matches!(
err,
TdsConstructionError::ValidationError { source: TdsError::InconsistentDataStructure {
message
} } if message.contains("Failed to derive periodic facet key")
);
assert_eq!(tds.number_of_simplices(), 1);
assert_eq!(tds.generation(), generation_before);
assert_eq!(tds.simplex_key_from_uuid(&candidate_uuid), None);
}
#[test]
fn test_insert_simplex_with_mapping_rejects_duplicate_simplex_without_mutation() {
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();
tds.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.unwrap();
let candidate = Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap();
let candidate_uuid = candidate.uuid();
let existing_uuid = tds.simplices().next().unwrap().1.uuid();
let generation_before = tds.generation();
let err = tds.insert_simplex_with_mapping(candidate).unwrap_err();
assert_matches!(
err,
TdsConstructionError::ValidationError {
source: TdsError::DuplicateSimplices { simplex_pairs, .. }
} if simplex_pairs == [[existing_uuid, candidate_uuid]]
);
assert_eq!(tds.number_of_simplices(), 1);
assert_eq!(tds.generation(), generation_before);
assert_eq!(tds.simplex_key_from_uuid(&candidate_uuid), None);
}
#[test]
fn test_insert_simplex_with_mapping_rejects_third_incident_facet_without_mutation() {
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 v3 = tds
.insert_vertex_with_mapping(vertex!([0.0, -1.0]).unwrap())
.unwrap();
let v4 = tds
.insert_vertex_with_mapping(vertex!([1.0, 1.0]).unwrap())
.unwrap();
tds.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.unwrap();
tds.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(),
)
.unwrap();
let candidate = Simplex::try_new_with_data(vec![v0, v1, v4], None).unwrap();
let candidate_uuid = candidate.uuid();
let generation_before = tds.generation();
let err = tds.insert_simplex_with_mapping(candidate).unwrap_err();
match err {
TdsConstructionError::ValidationError {
source:
TdsError::FacetSharingViolation {
existing_incident_count,
attempted_incident_count,
max_incident_count,
candidate_simplex_uuid,
candidate_facet_index,
..
},
} => {
assert_eq!(existing_incident_count, 2);
assert_eq!(attempted_incident_count, 3);
assert_eq!(max_incident_count, 2);
assert_eq!(candidate_simplex_uuid, candidate_uuid);
assert_eq!(candidate_facet_index, 2);
}
other => panic!("expected structured facet-sharing violation, got {other:?}"),
}
assert_eq!(tds.number_of_simplices(), 2);
assert_eq!(tds.generation(), generation_before);
assert_eq!(tds.simplex_key_from_uuid(&candidate_uuid), None);
}
#[test]
fn test_removed_simplices_do_not_block_future_simplex_insertions() {
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 v3 = tds
.insert_vertex_with_mapping(vertex!([0.0, -1.0]).unwrap())
.unwrap();
let v4 = tds
.insert_vertex_with_mapping(vertex!([1.0, 1.0]).unwrap())
.unwrap();
let removed = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.unwrap();
let second = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v3], None).unwrap(),
)
.unwrap();
assert_eq!(tds.remove_simplices_by_keys(&[removed]).unwrap(), 1);
tds.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.expect("removed duplicate simplex should not block reinsertion");
assert_eq!(tds.remove_simplices_by_keys(&[second]).unwrap(), 1);
tds.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v4], None).unwrap(),
)
.expect("removed incident simplex should not block later facet sharing");
}
#[test]
fn test_remove_simplices_by_keys_batch_repairs_incidence_and_neighbors() {
let mut tds = two_tetrahedra_tds_3d();
assert!(tds.number_of_simplices() > 1);
let first_ck = tds.simplex_keys().next().unwrap();
let removed = tds.remove_simplices_by_keys(&[first_ck]).unwrap();
assert_eq!(removed, 1);
for (vk, v) in tds.vertices() {
if let Some(ic) = v.incident_simplex() {
assert!(
tds.contains_simplex(ic),
"Vertex {vk:?} has dangling incident_simplex after batch removal"
);
}
}
for (_, simplex) in tds.simplices() {
if let Some(neighbors) = simplex.neighbors() {
for nk in neighbors.flatten() {
assert_ne!(nk, first_ck, "Dangling neighbor pointer to removed simplex");
}
}
}
}
#[test]
fn test_assign_incident_simplices_errors_on_dangling_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.assign_incident_simplices().unwrap_err();
assert_matches!(err.as_tds_error(), TdsError::VertexNotFound { .. });
}
#[test]
fn test_normalize_coherent_orientation_handles_single_simplex() {
let mut tds = initial_simplex_tds_3d();
assert_eq!(tds.number_of_simplices(), 1);
assert!(tds.normalize_coherent_orientation().is_ok());
}
#[test]
fn test_normalize_coherent_orientation_multi_simplex() {
let mut tds = two_tetrahedra_tds_3d();
assert!(tds.number_of_simplices() > 1);
assert!(tds.normalize_coherent_orientation().is_ok());
}
#[test]
fn test_remove_simplices_by_keys_returns_zero_for_missing() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let stale = SimplexKey::from(KeyData::from_ffi(0xDEAD));
assert_eq!(tds.remove_simplices_by_keys(&[stale]).unwrap(), 0);
}
#[test]
fn test_remove_simplices_by_keys_deduplicates_request_keys() {
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 simplex = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.unwrap();
assert_eq!(
tds.remove_simplices_by_keys(&[simplex, simplex]).unwrap(),
1
);
assert!(!tds.contains_simplex(simplex));
assert_eq!(tds.number_of_simplices(), 0);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_remove_simplices_by_keys_repairs_local_topology() {
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!([1.0, 1.0]).unwrap())
.unwrap();
let v3 = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0]).unwrap())
.unwrap();
let removed_key = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v1, v2], None).unwrap(),
)
.unwrap();
let surviving_key = tds
.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![v0, v2, v3], None).unwrap(),
)
.unwrap();
tds.assign_neighbors().unwrap();
tds.assign_incident_simplices().unwrap();
assert!(
tds.simplices
.get(surviving_key)
.unwrap()
.neighbors()
.unwrap()
.any(|neighbor| neighbor == Some(removed_key))
);
let removed_uuid = tds.simplices.get(removed_key).unwrap().uuid();
let removed_vertices = tds.simplices.get(removed_key).unwrap().vertices().to_vec();
assert_eq!(tds.remove_simplices_by_keys(&[removed_key]).unwrap(), 1);
assert_eq!(removed_vertices, vec![v0, v1, v2]);
assert!(!tds.simplices.contains_key(removed_key));
assert!(tds.simplices.contains_key(surviving_key));
assert!(!tds.uuid_to_simplex_key.contains_key(&removed_uuid));
if let Some(mut neighbors) = tds.simplices.get(surviving_key).unwrap().neighbors() {
assert!(neighbors.all(|neighbor| neighbor != Some(removed_key)));
}
assert_ne!(
tds.vertices.get(v0).unwrap().incident_simplex(),
Some(removed_key)
);
assert_ne!(
tds.vertices.get(v1).unwrap().incident_simplex(),
Some(removed_key)
);
assert_ne!(
tds.vertices.get(v2).unwrap().incident_simplex(),
Some(removed_key)
);
assert_eq!(
tds.vertices.get(v0).unwrap().incident_simplex(),
Some(surviving_key)
);
assert_eq!(
tds.vertices.get(v2).unwrap().incident_simplex(),
Some(surviving_key)
);
}
#[test]
fn test_set_vertex_data_replaces_existing() {
let vertices: [Vertex<i32, 2>; 3] = [
vertex!([0.0, 0.0]; data = 10i32).unwrap(),
vertex!([1.0, 0.0]; data = 20).unwrap(),
vertex!([0.0, 1.0]; data = 30).unwrap(),
];
let mut tds: Tds<i32, (), 2> = tds_from_specs(&vertices, &[vec![0, 1, 2]]);
let key = tds.vertex_keys().next().unwrap();
let prev = tds.set_vertex_data(key, Some(99)).unwrap();
assert!(prev.is_some()); assert_eq!(tds.vertex(key).unwrap().data, Some(99));
}
#[test]
fn test_set_vertex_data_on_no_data_vertex() {
let vertices = [
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let mut tds: Tds<(), (), 2> = tds_from_specs(&vertices, &[vec![0, 1, 2]]);
let key = tds.vertex_keys().next().unwrap();
let prev = tds.set_vertex_data(key, Some(())).unwrap();
assert_eq!(prev, None);
assert_eq!(tds.vertex(key).unwrap().data, Some(()));
}
#[test]
fn test_set_vertex_data_invalid_key_returns_error() {
let mut tds: Tds<i32, (), 2> = Tds::empty();
let stale = VertexKey::from(KeyData::from_ffi(0xDEAD));
let err = tds.set_vertex_data(stale, Some(1)).unwrap_err();
assert_matches!(err.as_tds_error(), TdsError::VertexNotFound { .. });
}
#[test]
fn test_set_simplex_data_on_empty_simplex() {
let vertices = [
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let mut tds: Tds<(), i32, 2> = tds_from_specs(&vertices, &[vec![0, 1, 2]]);
let key = tds.simplex_keys().next().unwrap();
let prev = tds.set_simplex_data(key, Some(42)).unwrap();
assert_eq!(prev, None); assert_eq!(tds.simplex(key).unwrap().data, Some(42));
}
#[test]
fn test_set_simplex_data_replaces_existing() {
let vertices = [
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let mut tds: Tds<(), i32, 2> = tds_from_specs(&vertices, &[vec![0, 1, 2]]);
let key = tds.simplex_keys().next().unwrap();
tds.set_simplex_data(key, Some(1)).unwrap();
let prev = tds.set_simplex_data(key, Some(2)).unwrap();
assert_eq!(prev, Some(1));
assert_eq!(tds.simplex(key).unwrap().data, Some(2));
}
#[test]
fn test_set_simplex_data_invalid_key_returns_error() {
let mut tds: Tds<(), i32, 2> = Tds::empty();
let stale = SimplexKey::from(KeyData::from_ffi(0xDEAD));
let err = tds.set_simplex_data(stale, Some(1)).unwrap_err();
assert_matches!(err.as_tds_error(), TdsError::SimplexNotFound { .. });
}
}