#![forbid(unsafe_code)]
use crate::core::{
collections::FacetToCellsMap, traits::DataType, triangulation_data_structure::Tds,
};
use crate::geometry::traits::coordinate::CoordinateScalar;
use crate::topology::{
characteristics::euler::{
FVector, TopologyClassification, count_simplices_with_facet_to_cells_map,
euler_characteristic, expected_chi_for,
},
traits::topological_space::TopologyError,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TopologyCheckResult {
pub chi: isize,
pub expected: Option<isize>,
pub classification: TopologyClassification,
pub counts: FVector,
pub notes: Vec<String>,
}
impl TopologyCheckResult {
#[must_use]
pub fn is_valid(&self) -> bool {
self.expected.is_none_or(|exp| self.chi == exp)
}
}
pub fn validate_triangulation_euler<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
) -> Result<TopologyCheckResult, TopologyError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let facet_to_cells = if tds.number_of_cells() == 0 {
FacetToCellsMap::default()
} else {
tds.build_facet_to_cells_map()
.map_err(|e| TopologyError::Counting(format!("Failed to build facet map: {e}")))?
};
Ok(validate_triangulation_euler_with_facet_to_cells_map(
tds,
&facet_to_cells,
))
}
pub(crate) fn validate_triangulation_euler_with_facet_to_cells_map<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
facet_to_cells: &FacetToCellsMap,
) -> TopologyCheckResult
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let counts = count_simplices_with_facet_to_cells_map(tds, facet_to_cells);
let chi = euler_characteristic(&counts);
let num_cells = tds.number_of_cells();
let classification = if num_cells == 0 {
TopologyClassification::Empty
} else if num_cells == 1 {
TopologyClassification::SingleSimplex(D)
} else if facet_to_cells.values().any(|cells| cells.len() == 1) {
TopologyClassification::Ball(D)
} else {
TopologyClassification::ClosedSphere(D)
};
let expected = expected_chi_for(&classification);
let mut notes = Vec::new();
if let Some(exp) = expected.filter(|&exp| chi != exp) {
notes.push(format!(
"Euler characteristic mismatch: computed {chi}, expected {exp}"
));
}
TopologyCheckResult {
chi,
expected,
classification,
counts,
notes,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::topology::characteristics::euler::TopologyClassification;
#[test]
fn test_topology_check_result_is_valid() {
let valid_result = TopologyCheckResult {
chi: 1,
expected: Some(1),
classification: TopologyClassification::Ball(3),
counts: FVector {
by_dim: vec![4, 6, 4, 1],
},
notes: vec![],
};
assert!(valid_result.is_valid());
let invalid_result = TopologyCheckResult {
chi: 0,
expected: Some(1),
classification: TopologyClassification::Ball(3),
counts: FVector {
by_dim: vec![4, 6, 4, 1],
},
notes: vec!["Mismatch".to_string()],
};
assert!(!invalid_result.is_valid());
let unknown_result = TopologyCheckResult {
chi: 42,
expected: None,
classification: TopologyClassification::Unknown,
counts: FVector { by_dim: vec![1] },
notes: vec![],
};
assert!(unknown_result.is_valid()); }
}