use crate::core::collections::{
MAX_PRACTICAL_DIMENSION_SIZE, SmallBuffer, VertexToSimplicesMap, fast_hash_map_with_capacity,
};
use crate::core::tds::errors::TdsError;
use crate::core::tds::{SimplexKey, VertexKey};
#[derive(Clone, Debug, Default)]
pub(crate) struct VertexIncidenceIndex {
map: VertexToSimplicesMap,
}
#[derive(Clone, Debug)]
pub(super) struct SimplexIncidenceRemoval {
simplex_key: SimplexKey,
removed_vertices: SmallBuffer<RemovedVertexIncidence, MAX_PRACTICAL_DIMENSION_SIZE>,
}
#[derive(Clone, Copy, Debug)]
struct RemovedVertexIncidence {
vertex_key: VertexKey,
position: usize,
}
impl VertexIncidenceIndex {
#[must_use]
pub(super) fn with_vertex_capacity(vertex_capacity: usize) -> Self {
Self {
map: fast_hash_map_with_capacity(vertex_capacity),
}
}
#[must_use]
pub(in crate::core) const fn as_map(&self) -> &VertexToSimplicesMap {
&self.map
}
#[must_use]
#[cfg(test)]
pub(in crate::core) fn is_empty(&self) -> bool {
self.map.is_empty()
}
#[must_use]
#[cfg(test)]
pub(in crate::core) fn contains_vertex(&self, vertex_key: VertexKey) -> bool {
self.map.contains_key(&vertex_key)
}
pub(in crate::core) fn simplex_keys(
&self,
vertex_key: VertexKey,
) -> impl Iterator<Item = SimplexKey> + '_ {
self.map
.get(&vertex_key)
.into_iter()
.flat_map(|simplices| simplices.iter().copied())
}
#[must_use]
#[inline]
pub(in crate::core) fn first_simplex(&self, vertex_key: VertexKey) -> Option<SimplexKey> {
self.map
.get(&vertex_key)
.and_then(|simplices| simplices.first().copied())
}
#[must_use]
pub(in crate::core) fn number_of_simplices(&self, vertex_key: VertexKey) -> usize {
let Some(incident_simplices) = self.map.get(&vertex_key) else {
return 0;
};
incident_simplices.len()
}
pub(super) fn insert_vertex(&mut self, vertex_key: VertexKey) -> Result<(), TdsError> {
if self.map.contains_key(&vertex_key) {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Vertex-to-simplices index already has an entry for vertex {vertex_key:?}"
),
});
}
self.map.insert(
vertex_key,
SmallBuffer::<SimplexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new(),
);
Ok(())
}
pub(super) fn remove_isolated_vertex(&mut self, vertex_key: VertexKey) -> Result<(), TdsError> {
let Some(incident_simplices) = self.map.get(&vertex_key) else {
return Err(TdsError::VertexNotFound {
vertex_key,
context: "vertex-to-simplices index removal".to_string(),
});
};
if !incident_simplices.is_empty() {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Cannot remove vertex {vertex_key:?} from incidence index while it still has {} incident simplices",
incident_simplices.len()
),
});
}
self.map.remove(&vertex_key);
Ok(())
}
pub(super) fn insert_simplex(
&mut self,
simplex_key: SimplexKey,
vertices: &[VertexKey],
) -> Result<(), TdsError> {
let mut inserted_vertices =
SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::with_capacity(vertices.len());
for &vertex_key in vertices {
if inserted_vertices.contains(&vertex_key) {
continue;
}
let Some(incident_simplices) = self.map.get_mut(&vertex_key) else {
self.rollback_inserted_simplex(simplex_key, &inserted_vertices);
return Err(TdsError::VertexNotFound {
vertex_key,
context: format!(
"registering simplex {simplex_key:?} in vertex incidence index"
),
});
};
if incident_simplices.contains(&simplex_key) {
self.rollback_inserted_simplex(simplex_key, &inserted_vertices);
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Vertex-to-simplices index already lists simplex {simplex_key:?} for vertex {vertex_key:?}"
),
});
}
incident_simplices.push(simplex_key);
inserted_vertices.push(vertex_key);
}
Ok(())
}
pub(super) fn remove_simplex(
&mut self,
simplex_key: SimplexKey,
vertices: &[VertexKey],
) -> Result<SimplexIncidenceRemoval, TdsError> {
let mut removal = SimplexIncidenceRemoval {
simplex_key,
removed_vertices:
SmallBuffer::<RemovedVertexIncidence, MAX_PRACTICAL_DIMENSION_SIZE>::with_capacity(
vertices.len(),
),
};
for &vertex_key in vertices {
if removal
.removed_vertices
.iter()
.any(|removed| removed.vertex_key == vertex_key)
{
continue;
}
let Some(incident_simplices) = self.map.get_mut(&vertex_key) else {
self.rollback_removed_simplex(&removal);
return Err(TdsError::VertexNotFound {
vertex_key,
context: format!(
"removing simplex {simplex_key:?} from vertex incidence index"
),
});
};
let Some(position) = incident_simplices
.iter()
.position(|candidate| *candidate == simplex_key)
else {
self.rollback_removed_simplex(&removal);
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Vertex-to-simplices index does not list simplex {simplex_key:?} for vertex {vertex_key:?}"
),
});
};
incident_simplices.swap_remove(position);
removal.removed_vertices.push(RemovedVertexIncidence {
vertex_key,
position,
});
}
Ok(removal)
}
fn rollback_inserted_simplex(&mut self, simplex_key: SimplexKey, vertices: &[VertexKey]) {
for &vertex_key in vertices {
if let Some(incident_simplices) = self.map.get_mut(&vertex_key)
&& let Some(position) = incident_simplices
.iter()
.position(|candidate| *candidate == simplex_key)
{
incident_simplices.swap_remove(position);
}
}
}
pub(super) fn rollback_removed_simplex(&mut self, removal: &SimplexIncidenceRemoval) {
for removed_vertex in removal.removed_vertices.iter().rev() {
if let Some(incident_simplices) = self.map.get_mut(&removed_vertex.vertex_key) {
if removed_vertex.position < incident_simplices.len() {
let displaced_simplex = incident_simplices[removed_vertex.position];
incident_simplices.push(displaced_simplex);
incident_simplices[removed_vertex.position] = removal.simplex_key;
} else {
incident_simplices.push(removal.simplex_key);
}
}
}
}
#[cfg(test)]
pub(super) fn clear_vertex_for_test(&mut self, vertex_key: VertexKey) {
if let Some(incident_simplices) = self.map.get_mut(&vertex_key) {
incident_simplices.clear();
}
}
}
#[cfg(test)]
mod tests {
use std::assert_matches;
use super::*;
use slotmap::KeyData;
fn vertex_key(raw: u64) -> VertexKey {
VertexKey::from(KeyData::from_ffi(raw))
}
fn simplex_key(raw: u64) -> SimplexKey {
SimplexKey::from(KeyData::from_ffi(raw))
}
#[test]
fn insert_vertex_rejects_duplicate_entry() {
let mut index = VertexIncidenceIndex::default();
let vertex = vertex_key(1);
index.insert_vertex(vertex).unwrap();
let err = index.insert_vertex(vertex).unwrap_err();
assert_matches!(err, TdsError::InconsistentDataStructure { .. });
assert!(index.contains_vertex(vertex));
assert_eq!(index.number_of_simplices(vertex), 0);
}
#[test]
fn insert_simplex_rejects_missing_vertex_entry() {
let mut index = VertexIncidenceIndex::default();
let err = index
.insert_simplex(simplex_key(1), &[vertex_key(1)])
.unwrap_err();
assert_matches!(err, TdsError::VertexNotFound { .. });
}
#[test]
fn insert_simplex_rolls_back_when_later_vertex_entry_is_missing() {
let mut index = VertexIncidenceIndex::default();
let existing = vertex_key(1);
let missing = vertex_key(2);
let simplex = simplex_key(1);
index.insert_vertex(existing).unwrap();
let err = index
.insert_simplex(simplex, &[existing, missing])
.unwrap_err();
assert_matches!(err, TdsError::VertexNotFound { .. });
assert_eq!(index.number_of_simplices(existing), 0);
}
#[test]
fn insert_simplex_records_repeated_vertex_key_once() {
let mut index = VertexIncidenceIndex::default();
let vertex = vertex_key(1);
let simplex = simplex_key(1);
index.insert_vertex(vertex).unwrap();
index
.insert_simplex(simplex, &[vertex, vertex])
.expect("periodic lifted slots share one canonical incidence");
assert_eq!(index.number_of_simplices(vertex), 1);
assert_eq!(
index.simplex_keys(vertex).collect::<Vec<_>>(),
vec![simplex]
);
}
#[test]
fn first_simplex_returns_one_incident_simplex_without_scanning() {
let mut index = VertexIncidenceIndex::default();
let vertex = vertex_key(1);
let isolated = vertex_key(2);
let first = simplex_key(1);
let second = simplex_key(2);
index.insert_vertex(vertex).unwrap();
index.insert_vertex(isolated).unwrap();
index.insert_simplex(first, &[vertex]).unwrap();
index.insert_simplex(second, &[vertex]).unwrap();
assert_eq!(index.first_simplex(vertex), Some(first));
assert_eq!(index.first_simplex(isolated), None);
assert_eq!(index.first_simplex(vertex_key(3)), None);
}
#[test]
fn remove_isolated_vertex_rejects_non_isolated_entry() {
let mut index = VertexIncidenceIndex::default();
let vertex = vertex_key(1);
let simplex = simplex_key(1);
index.insert_vertex(vertex).unwrap();
index.insert_simplex(simplex, &[vertex]).unwrap();
let err = index.remove_isolated_vertex(vertex).unwrap_err();
assert_matches!(err, TdsError::InconsistentDataStructure { .. });
}
#[test]
fn remove_isolated_vertex_rejects_missing_entry() {
let mut index = VertexIncidenceIndex::default();
let err = index.remove_isolated_vertex(vertex_key(1)).unwrap_err();
assert_matches!(err, TdsError::VertexNotFound { .. });
assert!(index.is_empty());
}
#[test]
fn remove_simplex_removes_repeated_vertex_key_once() {
let mut index = VertexIncidenceIndex::default();
let vertex = vertex_key(1);
let simplex = simplex_key(1);
index.insert_vertex(vertex).unwrap();
index.insert_simplex(simplex, &[vertex]).unwrap();
let removal = index
.remove_simplex(simplex, &[vertex, vertex])
.expect("periodic lifted slots remove one canonical incidence");
assert_eq!(removal.removed_vertices.len(), 1);
assert_eq!(index.number_of_simplices(vertex), 0);
}
#[test]
fn remove_simplex_rolls_back_order_exactly_when_later_vertex_is_missing() {
let mut index = VertexIncidenceIndex::default();
let vertex = vertex_key(1);
let missing = vertex_key(2);
let before = [simplex_key(1), simplex_key(2), simplex_key(3)];
index.insert_vertex(vertex).unwrap();
for simplex in before {
index.insert_simplex(simplex, &[vertex]).unwrap();
}
let err = index
.remove_simplex(before[1], &[vertex, missing])
.unwrap_err();
assert_matches!(err, TdsError::VertexNotFound { .. });
assert_eq!(
index.simplex_keys(vertex).collect::<Vec<_>>(),
before.to_vec()
);
}
#[test]
fn rollback_removed_simplex_restores_multiple_removals_exactly() {
let mut index = VertexIncidenceIndex::default();
let vertex = vertex_key(1);
let before = [
simplex_key(1),
simplex_key(2),
simplex_key(3),
simplex_key(4),
];
index.insert_vertex(vertex).unwrap();
for simplex in before {
index.insert_simplex(simplex, &[vertex]).unwrap();
}
let first_removal = index.remove_simplex(before[1], &[vertex]).unwrap();
let second_removal = index.remove_simplex(before[2], &[vertex]).unwrap();
index.rollback_removed_simplex(&second_removal);
index.rollback_removed_simplex(&first_removal);
assert_eq!(
index.simplex_keys(vertex).collect::<Vec<_>>(),
before.to_vec()
);
}
#[test]
fn simplex_incidence_round_trip_updates_only_listed_vertices() {
let mut index = VertexIncidenceIndex::default();
let a = vertex_key(1);
let b = vertex_key(2);
let isolated = vertex_key(3);
let simplex = simplex_key(1);
index.insert_vertex(a).unwrap();
index.insert_vertex(b).unwrap();
index.insert_vertex(isolated).unwrap();
index.insert_simplex(simplex, &[a, b]).unwrap();
assert_eq!(index.simplex_keys(a).collect::<Vec<_>>(), vec![simplex]);
assert_eq!(index.simplex_keys(b).collect::<Vec<_>>(), vec![simplex]);
assert_eq!(index.number_of_simplices(isolated), 0);
index.remove_simplex(simplex, &[a, b]).unwrap();
assert_eq!(index.number_of_simplices(a), 0);
assert_eq!(index.number_of_simplices(b), 0);
assert_eq!(index.number_of_simplices(isolated), 0);
}
}