#![forbid(unsafe_code)]
use super::{
Tds, TdsConstructionError, TdsDraft, TdsDraftError, TdsError, TdsMutationError, VertexKey,
};
use crate::core::collections::{
Entry, FastHashMap, SimplexVertexKeyBuffer, fast_hash_map_with_capacity,
};
use crate::core::facet::facet_key_from_vertices;
use crate::core::simplex::{Simplex, SimplexValidationError};
use crate::core::vertex::Vertex;
use std::marker::PhantomData;
use thiserror::Error;
#[derive(Clone, Debug, Error, PartialEq)]
pub(crate) enum ExplicitSimplexParseError {
#[error("no simplices provided for nonempty TDS construction")]
EmptySimplices,
#[error(
"simplex {simplex_index} has {actual} vertex indices, expected {expected} for a simplex"
)]
InvalidSimplexArity {
simplex_index: usize,
actual: usize,
expected: usize,
},
#[error(
"simplex {simplex_index} references vertex index {vertex_index}, but the vertex count is {bound}"
)]
IndexOutOfBounds {
simplex_index: usize,
vertex_index: usize,
bound: usize,
},
#[error("simplex {simplex_index} contains duplicate vertex index {vertex_index}")]
DuplicateVertexInSimplex {
simplex_index: usize,
vertex_index: usize,
},
}
impl From<ExplicitSimplexParseError> for TdsBuilderError {
fn from(source: ExplicitSimplexParseError) -> Self {
match source {
ExplicitSimplexParseError::EmptySimplices => Self::EmptySimplices,
ExplicitSimplexParseError::InvalidSimplexArity {
simplex_index,
actual,
expected,
} => Self::InvalidSimplexArity {
simplex_index,
actual,
expected,
},
ExplicitSimplexParseError::IndexOutOfBounds {
simplex_index,
vertex_index,
bound,
} => Self::IndexOutOfBounds {
simplex_index,
vertex_index,
bound,
},
ExplicitSimplexParseError::DuplicateVertexInSimplex {
simplex_index,
vertex_index,
} => Self::DuplicateVertexInSimplex {
simplex_index,
vertex_index,
},
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct ParsedTdsInput<'a, U, const D: usize> {
vertices: &'a [Vertex<U, D>],
simplices: &'a [Vec<usize>],
}
impl<'a, U, const D: usize> ParsedTdsInput<'a, U, D> {
pub(crate) fn try_new(
vertices: &'a [Vertex<U, D>],
simplices: &'a [Vec<usize>],
) -> Result<Self, ExplicitSimplexParseError> {
if simplices.is_empty() && !vertices.is_empty() {
return Err(ExplicitSimplexParseError::EmptySimplices);
}
for (simplex_index, simplex) in simplices.iter().enumerate() {
if simplex.len() != D + 1 {
return Err(ExplicitSimplexParseError::InvalidSimplexArity {
simplex_index,
actual: simplex.len(),
expected: D + 1,
});
}
for (offset, &vertex_index) in simplex.iter().enumerate() {
if vertex_index >= vertices.len() {
return Err(ExplicitSimplexParseError::IndexOutOfBounds {
simplex_index,
vertex_index,
bound: vertices.len(),
});
}
if simplex[..offset].contains(&vertex_index) {
return Err(ExplicitSimplexParseError::DuplicateVertexInSimplex {
simplex_index,
vertex_index,
});
}
}
}
Ok(Self {
vertices,
simplices,
})
}
}
#[derive(Clone, Copy, Debug)]
enum TdsBuilderInput<'a, U, const D: usize> {
Raw {
vertices: &'a [Vertex<U, D>],
simplices: &'a [Vec<usize>],
},
Parsed(ParsedTdsInput<'a, U, D>),
}
impl<'a, U, const D: usize> TdsBuilderInput<'a, U, D> {
const fn vertices(&self) -> &'a [Vertex<U, D>] {
match self {
Self::Raw { vertices, .. } | Self::Parsed(ParsedTdsInput { vertices, .. }) => vertices,
}
}
const fn simplices(&self) -> &'a [Vec<usize>] {
match self {
Self::Raw { simplices, .. } | Self::Parsed(ParsedTdsInput { simplices, .. }) => {
simplices
}
}
}
fn parse(self) -> Result<ParsedTdsInput<'a, U, D>, ExplicitSimplexParseError> {
match self {
Self::Raw {
vertices,
simplices,
} => ParsedTdsInput::try_new(vertices, simplices),
Self::Parsed(parsed) => Ok(parsed),
}
}
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum TdsBuilderError {
#[error("no simplices provided for nonempty TDS construction")]
EmptySimplices,
#[error(
"simplex {simplex_index} has {actual} vertex indices, expected {expected} for a simplex"
)]
InvalidSimplexArity {
simplex_index: usize,
actual: usize,
expected: usize,
},
#[error(
"simplex {simplex_index} references vertex index {vertex_index}, but the vertex count is {bound}"
)]
IndexOutOfBounds {
simplex_index: usize,
vertex_index: usize,
bound: usize,
},
#[error("simplex {simplex_index} contains duplicate vertex index {vertex_index}")]
DuplicateVertexInSimplex {
simplex_index: usize,
vertex_index: usize,
},
#[error("vertex {vertex_index} could not be inserted during TDS construction: {source}")]
VertexInsertion {
vertex_index: usize,
#[source]
source: Box<TdsConstructionError>,
},
#[error("explicit TDS topology is invalid: {source}")]
TopologyValidation {
#[source]
source: Box<TdsConstructionError>,
},
#[error("simplex {simplex_index} could not be created during TDS construction: {source}")]
SimplexCreation {
simplex_index: usize,
#[source]
source: SimplexValidationError,
},
#[error("simplex {simplex_index} could not be inserted during TDS construction: {source}")]
SimplexInsertion {
simplex_index: usize,
#[source]
source: Box<TdsConstructionError>,
},
#[error("neighbor assignment failed during TDS construction: {source}")]
NeighborAssignment {
#[source]
source: Box<TdsError>,
},
#[error("incident-simplex assignment failed during TDS construction: {source}")]
IncidentAssignment {
#[source]
source: Box<TdsMutationError>,
},
#[error("orientation normalization failed during TDS construction: {source}")]
OrientationNormalization {
#[source]
source: Box<TdsError>,
},
#[error("Levels 1-2 validation failed during TDS construction: {source}")]
Validation {
#[source]
source: Box<TdsError>,
},
}
#[derive(Clone, Copy, Debug)]
pub struct TdsBuilder<'a, U, const D: usize, V = ()> {
input: TdsBuilderInput<'a, U, D>,
_simplex_data: PhantomData<V>,
}
impl<'a, U, const D: usize> TdsBuilder<'a, U, D> {
#[must_use]
pub const fn new(vertices: &'a [Vertex<U, D>], simplices: &'a [Vec<usize>]) -> Self {
Self {
input: TdsBuilderInput::Raw {
vertices,
simplices,
},
_simplex_data: PhantomData,
}
}
pub(crate) const fn from_parsed(input: ParsedTdsInput<'a, U, D>) -> Self {
Self {
input: TdsBuilderInput::Parsed(input),
_simplex_data: PhantomData,
}
}
}
impl<'a, U, V, const D: usize> TdsBuilder<'a, U, D, V> {
#[must_use]
pub const fn vertex_count(&self) -> usize {
self.input.vertices().len()
}
#[must_use]
pub const fn simplex_count(&self) -> usize {
self.input.simplices().len()
}
#[must_use]
pub const fn simplex_data_type<W>(self) -> TdsBuilder<'a, U, D, W> {
TdsBuilder {
input: self.input,
_simplex_data: PhantomData,
}
}
pub fn build(self) -> Result<Tds<U, V, D>, TdsBuilderError>
where
U: Clone,
{
let parsed = self.input.parse().map_err(TdsBuilderError::from)?;
let vertices = parsed.vertices;
let simplices = parsed.simplices;
let mut draft = TdsDraft::new();
let mut index_to_key = Vec::with_capacity(vertices.len());
for (vertex_index, vertex) in vertices.iter().cloned().enumerate() {
let vertex_key =
draft
.insert_vertex(vertex)
.map_err(|source| TdsBuilderError::VertexInsertion {
vertex_index,
source: Box::new(source),
})?;
index_to_key.push(vertex_key);
}
Self::validate_topology(simplices, &index_to_key).map_err(|source| {
TdsBuilderError::TopologyValidation {
source: Box::new(source),
}
})?;
for (simplex_index, simplex_spec) in simplices.iter().enumerate() {
let vertex_keys = Self::simplex_vertex_keys(simplex_spec, &index_to_key);
let simplex = Simplex::try_new(vertex_keys).map_err(|source| {
TdsBuilderError::SimplexCreation {
simplex_index,
source,
}
})?;
draft
.insert_simplex_prechecked_topology(simplex)
.map_err(|source| TdsBuilderError::SimplexInsertion {
simplex_index,
source: Box::new(source),
})?;
}
draft.finish().map_err(|source| match source {
TdsDraftError::NeighborAssignment { source } => {
TdsBuilderError::NeighborAssignment { source }
}
TdsDraftError::IncidentAssignment { source } => {
TdsBuilderError::IncidentAssignment { source }
}
TdsDraftError::OrientationNormalization { source } => {
TdsBuilderError::OrientationNormalization { source }
}
TdsDraftError::Validation { source } => TdsBuilderError::Validation { source },
})
}
fn validate_topology(
simplices: &[Vec<usize>],
index_to_key: &[VertexKey],
) -> Result<(), TdsConstructionError> {
Self::reject_duplicate_simplices(simplices, index_to_key)?;
Self::reject_overshared_facets(simplices, index_to_key)?;
Ok(())
}
fn reject_duplicate_simplices(
simplices: &[Vec<usize>],
index_to_key: &[VertexKey],
) -> Result<(), TdsConstructionError> {
let mut seen: FastHashMap<SimplexVertexKeyBuffer, usize> =
fast_hash_map_with_capacity(simplices.len());
for (simplex_index, simplex_spec) in simplices.iter().enumerate() {
let mut identity = Self::simplex_vertex_keys(simplex_spec, index_to_key);
identity.as_mut_slice().sort_unstable();
match seen.entry(identity) {
Entry::Occupied(entry) => {
let mut vertex_indices = simplex_spec.clone();
vertex_indices.sort_unstable();
return Err(TdsConstructionError::ValidationError {
source: TdsError::DuplicateExplicitSimplices {
existing_simplex_index: *entry.get(),
duplicate_simplex_index: simplex_index,
vertex_indices,
},
});
}
Entry::Vacant(entry) => {
entry.insert(simplex_index);
}
}
}
Ok(())
}
fn reject_overshared_facets(
simplices: &[Vec<usize>],
index_to_key: &[VertexKey],
) -> Result<(), TdsConstructionError> {
let capacity = simplices.len().saturating_mul(D.saturating_add(1));
let mut incident_counts: FastHashMap<SimplexVertexKeyBuffer, usize> =
fast_hash_map_with_capacity(capacity);
for (simplex_index, simplex_spec) in simplices.iter().enumerate() {
for facet_index in 0..=D {
let mut facet_identity: SimplexVertexKeyBuffer = simplex_spec
.iter()
.enumerate()
.filter_map(|(local_index, &input_index)| {
(local_index != facet_index).then_some(index_to_key[input_index])
})
.collect();
facet_identity.as_mut_slice().sort_unstable();
match incident_counts.entry(facet_identity) {
Entry::Occupied(mut entry) => {
let incident_count = *entry.get();
if incident_count >= 2 {
let mut facet_vertex_indices: Vec<usize> = simplex_spec
.iter()
.enumerate()
.filter_map(|(local_index, &input_index)| {
(local_index != facet_index).then_some(input_index)
})
.collect();
facet_vertex_indices.sort_unstable();
return Err(TdsConstructionError::ValidationError {
source: TdsError::ExplicitFacetSharingViolation {
facet_key: facet_key_from_vertices(entry.key().as_slice()),
facet_vertex_indices,
existing_incident_count: incident_count,
attempted_incident_count: incident_count + 1,
max_incident_count: 2,
candidate_simplex_index: simplex_index,
candidate_facet_index: facet_index,
},
});
}
*entry.get_mut() += 1;
}
Entry::Vacant(entry) => {
entry.insert(1);
}
}
}
}
Ok(())
}
fn simplex_vertex_keys(
simplex_spec: &[usize],
index_to_key: &[VertexKey],
) -> SimplexVertexKeyBuffer {
simplex_spec
.iter()
.map(|&vertex_index| index_to_key[vertex_index])
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::tds::TriangulationConstructionState;
use crate::vertex;
use std::assert_matches;
fn two_triangle_fixture() -> ([Vertex<(), 2>; 4], [Vec<usize>; 2]) {
let vertices = [
vertex![0.0, 0.0].unwrap(),
vertex![1.0, 0.0].unwrap(),
vertex![0.0, 1.0].unwrap(),
vertex![1.0, 0.2].unwrap(),
];
let simplices = [vec![0, 1, 3], vec![0, 2, 3]];
(vertices, simplices)
}
#[test]
fn build_publishes_only_a_complete_valid_tds() {
let (vertices, simplices) = two_triangle_fixture();
let builder = TdsBuilder::new(&vertices, &simplices).simplex_data_type::<usize>();
assert_eq!(builder.vertex_count(), 4);
assert_eq!(builder.simplex_count(), 2);
let tds = builder.build().unwrap();
assert_matches!(
tds.construction_state(),
TriangulationConstructionState::Constructed
);
assert!(tds.validate().is_ok());
assert_eq!(tds.number_of_vertices(), 4);
assert_eq!(tds.number_of_simplices(), 2);
assert!(tds.simplices().all(|(_, simplex)| simplex.data().is_none()));
}
#[test]
fn build_rejects_raw_specs_before_assembly() {
let vertices = [
vertex![0.0, 0.0].unwrap(),
vertex![1.0, 0.0].unwrap(),
vertex![0.0, 1.0].unwrap(),
];
assert_matches!(
TdsBuilder::new(&vertices, &[]).build(),
Err(TdsBuilderError::EmptySimplices)
);
assert_matches!(
TdsBuilder::new(&vertices, &[vec![0, 1]]).build(),
Err(TdsBuilderError::InvalidSimplexArity {
simplex_index: 0,
actual: 2,
expected: 3,
})
);
assert_matches!(
TdsBuilder::new(&vertices, &[vec![0, 1, 3]]).build(),
Err(TdsBuilderError::IndexOutOfBounds {
simplex_index: 0,
vertex_index: 3,
bound: 3,
})
);
assert_matches!(
TdsBuilder::new(&vertices, &[vec![0, 1, 1]]).build(),
Err(TdsBuilderError::DuplicateVertexInSimplex {
simplex_index: 0,
vertex_index: 1,
})
);
}
#[test]
fn build_reports_the_input_index_for_a_duplicate_vertex_uuid() {
let repeated = vertex![0.0, 0.0].unwrap();
let repeated_uuid = repeated.uuid();
let vertices = [repeated, repeated, vertex![0.0, 1.0].unwrap()];
let simplices = [vec![0, 1, 2]];
assert_matches!(
TdsBuilder::new(&vertices, &simplices).build(),
Err(TdsBuilderError::VertexInsertion {
vertex_index: 1,
source,
}) if matches!(
source.as_ref(),
TdsConstructionError::DuplicateUuid {
entity: crate::core::tds::EntityKind::Vertex,
uuid,
} if *uuid == repeated_uuid
)
);
}
#[test]
fn build_accepts_the_vacuously_valid_empty_complex() {
let vertices: [Vertex<(), 2>; 0] = [];
let simplices: [Vec<usize>; 0] = [];
let tds = TdsBuilder::new(&vertices, &simplices).build().unwrap();
assert_eq!(tds.number_of_vertices(), 0);
assert_eq!(tds.number_of_simplices(), 0);
assert!(tds.validate().is_ok());
}
#[test]
fn parsed_input_flows_into_assembly_without_returning_to_raw_state() {
let (vertices, simplices) = two_triangle_fixture();
let parsed = ParsedTdsInput::try_new(&vertices, &simplices).unwrap();
let tds = TdsBuilder::from_parsed(parsed).build().unwrap();
assert_eq!(tds.number_of_vertices(), vertices.len());
assert_eq!(tds.number_of_simplices(), simplices.len());
assert!(tds.validate().is_ok());
}
}