#![forbid(unsafe_code)]
use crate::core::tds::{TdsError, TdsInvariantKind, TdsInvariantViolation, TdsValidationReport};
use crate::triangulation::realization::TriangulationRealizationValidationError;
use crate::triangulation::validation::TriangulationValidationError;
use crate::validation::DelaunayTriangulationValidationError;
use thiserror::Error;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum InvariantKind {
VertexValidity,
SimplexValidity,
SimplexCoordinateUniqueness,
VertexMappings,
SimplexMappings,
SimplexVertexKeys,
VertexIncidence,
VertexToSimplicesIndex,
DuplicateSimplices,
FacetSharing,
NeighborConsistency,
CoherentOrientation,
Connectedness,
Topology,
Realization,
DelaunayProperty,
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum InvariantError {
#[error(transparent)]
Tds {
#[from]
source: TdsError,
},
#[error(transparent)]
Triangulation {
#[from]
source: TriangulationValidationError,
},
#[error(transparent)]
Realization {
#[from]
source: TriangulationRealizationValidationError,
},
#[error(transparent)]
Delaunay {
#[from]
source: DelaunayTriangulationValidationError,
},
}
#[derive(Clone, Debug)]
pub struct InvariantViolation {
pub kind: InvariantKind,
pub error: InvariantError,
}
#[derive(Clone, Debug)]
pub struct TriangulationValidationReport {
pub violations: Vec<InvariantViolation>,
}
impl TriangulationValidationReport {
#[must_use]
pub const fn is_empty(&self) -> bool {
self.violations.is_empty()
}
}
impl From<TdsInvariantKind> for InvariantKind {
fn from(kind: TdsInvariantKind) -> Self {
match kind {
TdsInvariantKind::VertexValidity => Self::VertexValidity,
TdsInvariantKind::SimplexValidity => Self::SimplexValidity,
TdsInvariantKind::SimplexCoordinateUniqueness => Self::SimplexCoordinateUniqueness,
TdsInvariantKind::VertexMappings => Self::VertexMappings,
TdsInvariantKind::SimplexMappings => Self::SimplexMappings,
TdsInvariantKind::SimplexVertexKeys => Self::SimplexVertexKeys,
TdsInvariantKind::VertexIncidence => Self::VertexIncidence,
TdsInvariantKind::VertexToSimplicesIndex => Self::VertexToSimplicesIndex,
TdsInvariantKind::DuplicateSimplices => Self::DuplicateSimplices,
TdsInvariantKind::FacetSharing => Self::FacetSharing,
TdsInvariantKind::NeighborConsistency => Self::NeighborConsistency,
TdsInvariantKind::CoherentOrientation => Self::CoherentOrientation,
}
}
}
impl From<TdsInvariantViolation> for InvariantViolation {
fn from(violation: TdsInvariantViolation) -> Self {
Self {
kind: violation.kind.into(),
error: violation.error.into(),
}
}
}
impl From<TdsValidationReport> for TriangulationValidationReport {
fn from(report: TdsValidationReport) -> Self {
Self {
violations: report.violations.into_iter().map(Into::into).collect(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::topology::characteristics::euler::TopologyClassification;
use crate::topology::traits::topological_space::TopologyKind;
#[test]
fn tds_report_promotion_preserves_order_categories_and_typed_sources() {
let cases = [
(
TdsInvariantKind::VertexValidity,
InvariantKind::VertexValidity,
),
(
TdsInvariantKind::SimplexValidity,
InvariantKind::SimplexValidity,
),
(
TdsInvariantKind::SimplexCoordinateUniqueness,
InvariantKind::SimplexCoordinateUniqueness,
),
(
TdsInvariantKind::VertexMappings,
InvariantKind::VertexMappings,
),
(
TdsInvariantKind::SimplexMappings,
InvariantKind::SimplexMappings,
),
(
TdsInvariantKind::SimplexVertexKeys,
InvariantKind::SimplexVertexKeys,
),
(
TdsInvariantKind::VertexIncidence,
InvariantKind::VertexIncidence,
),
(
TdsInvariantKind::VertexToSimplicesIndex,
InvariantKind::VertexToSimplicesIndex,
),
(
TdsInvariantKind::DuplicateSimplices,
InvariantKind::DuplicateSimplices,
),
(TdsInvariantKind::FacetSharing, InvariantKind::FacetSharing),
(
TdsInvariantKind::NeighborConsistency,
InvariantKind::NeighborConsistency,
),
(
TdsInvariantKind::CoherentOrientation,
InvariantKind::CoherentOrientation,
),
];
let original = TdsValidationReport {
violations: cases
.iter()
.enumerate()
.map(|(index, &(kind, _))| TdsInvariantViolation {
kind,
error: TdsError::IndexOutOfBounds {
index,
bound: 0,
context: "report promotion".to_string(),
},
})
.collect(),
};
let promoted = TriangulationValidationReport::from(original.clone());
assert_eq!(promoted.violations.len(), original.violations.len());
for ((violation, original), (_, kind)) in promoted
.violations
.into_iter()
.zip(original.violations)
.zip(cases)
{
assert_eq!(violation.kind, kind);
assert_eq!(
violation.error,
InvariantError::Tds {
source: original.error
}
);
}
assert!(
TriangulationValidationReport::from(TdsValidationReport {
violations: Vec::new(),
})
.is_empty()
);
}
#[test]
fn higher_layer_promotions_preserve_typed_sources() {
let topology = TriangulationValidationError::EulerCharacteristicMismatch {
computed: 1,
expected: 2,
classification: TopologyClassification::Ball(3),
};
assert_eq!(
InvariantError::from(topology.clone()),
InvariantError::Triangulation { source: topology },
);
let realization = TriangulationRealizationValidationError::UnsupportedTopology {
topology: TopologyKind::Hyperbolic,
dimension: 2,
};
assert_eq!(
InvariantError::from(realization.clone()),
InvariantError::Realization {
source: realization
},
);
let delaunay = DelaunayTriangulationValidationError::Tds {
source: Box::new(TdsError::InconsistentDataStructure {
message: "typed source".to_string(),
}),
};
assert_eq!(
InvariantError::from(delaunay.clone()),
InvariantError::Delaunay { source: delaunay },
);
}
}