use crate::core::collections::{MAX_PRACTICAL_DIMENSION_SIZE, SmallBuffer};
use crate::core::simplex::Simplex;
use crate::core::tds::{Tds, VertexKey};
use crate::geometry::point::Point;
use slotmap::Key;
use thiserror::Error;
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub(crate) enum CanonicalSimplexPointError {
#[error("canonical simplex point collection expected {expected} vertices, found {found}")]
InvalidArity {
expected: usize,
found: usize,
},
#[error("vertex {vertex_key:?} not found while collecting canonical simplex predicate points")]
MissingVertex {
vertex_key: VertexKey,
},
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub(crate) enum CanonicalFacetPointError {
#[error("canonical facet point collection expected {expected} vertices, found {found}")]
InvalidArity {
expected: usize,
found: usize,
},
#[error("vertex {vertex_key:?} not found while collecting canonical facet predicate points")]
MissingVertex {
vertex_key: VertexKey,
},
}
pub(crate) fn sorted_simplex_points<U, V, const D: usize>(
tds: &Tds<U, V, D>,
simplex: &Simplex<V, D>,
) -> Result<SmallBuffer<Point<D>, MAX_PRACTICAL_DIMENSION_SIZE>, CanonicalSimplexPointError> {
let vertex_count = simplex.number_of_vertices();
if vertex_count != D + 1 {
return Err(CanonicalSimplexPointError::InvalidArity {
expected: D + 1,
found: vertex_count,
});
}
let mut keys: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
simplex.vertices().iter().copied().collect();
keys.sort_unstable_by_key(|vk| vk.data().as_ffi());
let mut points = SmallBuffer::with_capacity(keys.len());
for &vk in &keys {
let vertex = tds
.vertex(vk)
.ok_or(CanonicalSimplexPointError::MissingVertex { vertex_key: vk })?;
points.push(*vertex.point());
}
Ok(points)
}
pub(crate) fn sorted_facet_points_with_extra<U, V, const D: usize>(
tds: &Tds<U, V, D>,
facet_keys: &[VertexKey],
extra: Point<D>,
) -> Result<SmallBuffer<Point<D>, MAX_PRACTICAL_DIMENSION_SIZE>, CanonicalFacetPointError> {
if facet_keys.len() != D {
return Err(CanonicalFacetPointError::InvalidArity {
expected: D,
found: facet_keys.len(),
});
}
let mut sorted_keys: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
facet_keys.iter().copied().collect();
sorted_keys.sort_unstable_by_key(|vk| vk.data().as_ffi());
let mut points = SmallBuffer::with_capacity(sorted_keys.len() + 1);
for &vk in &sorted_keys {
let vertex = tds
.vertex(vk)
.ok_or(CanonicalFacetPointError::MissingVertex { vertex_key: vk })?;
points.push(*vertex.point());
}
points.push(extra);
Ok(points)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::kernel::{AdaptiveKernel, Kernel};
use crate::vertex;
use slotmap::KeyData;
use std::assert_matches;
fn build_tds_with_points<const D: usize>(
coords: &[[f64; D]],
) -> (Tds<(), (), D>, Vec<VertexKey>) {
let mut tds = Tds::<(), (), D>::empty();
let mut keys = Vec::with_capacity(coords.len());
for c in coords {
let v = vertex!(*c).expect("finite point coordinates");
let vk = tds
.insert_vertex_with_mapping(v)
.expect("insert should succeed");
keys.push(vk);
}
(tds, keys)
}
#[test]
fn test_sorted_simplex_points_produces_canonical_order() {
let (mut tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let simplex =
Simplex::try_new_with_data(keys.clone(), None::<()>).expect("simplex should be valid");
let simplex_key = tds
.insert_simplex_with_mapping(simplex)
.expect("insert should succeed");
let simplex_ref = tds.simplex(simplex_key).unwrap();
let points = sorted_simplex_points(&tds, simplex_ref).expect("should resolve all vertices");
let mut sorted_keys = keys;
sorted_keys.sort_unstable_by_key(|vk| vk.data().as_ffi());
for (i, &vk) in sorted_keys.iter().enumerate() {
let expected = *tds.vertex(vk).unwrap().point();
assert_eq!(points[i], expected);
}
}
#[test]
fn test_sorted_simplex_points_permutation_invariant() {
let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let simplex_a = Simplex::try_new_with_data(vec![keys[0], keys[1], keys[2]], None::<()>)
.expect("simplex should be valid");
let simplex_b = Simplex::try_new_with_data(vec![keys[2], keys[0], keys[1]], None::<()>)
.expect("simplex should be valid");
let points_a = sorted_simplex_points(&tds, &simplex_a).unwrap();
let points_b = sorted_simplex_points(&tds, &simplex_b).unwrap();
assert_eq!(points_a.as_slice(), points_b.as_slice());
}
#[test]
fn test_sorted_simplex_points_rejects_wrong_arity() {
let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let mut simplex = Simplex::try_new_with_data(vec![keys[0], keys[1], keys[2]], None::<()>)
.expect("simplex should be valid");
simplex.clear_vertex_keys();
simplex.push_vertex_key(keys[0]);
simplex.push_vertex_key(keys[1]);
let err = sorted_simplex_points(&tds, &simplex).unwrap_err();
assert_matches!(
err,
CanonicalSimplexPointError::InvalidArity {
expected: 3,
found: 2,
}
);
}
#[test]
fn test_sorted_simplex_points_reports_missing_vertex_key() {
let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0]]);
let missing = VertexKey::from(KeyData::from_ffi(999_999));
let simplex = Simplex::try_new_with_data(vec![keys[0], keys[1], missing], None::<()>)
.expect("simplex arity and uniqueness should be valid");
let err = sorted_simplex_points(&tds, &simplex).unwrap_err();
assert_matches!(
err,
CanonicalSimplexPointError::MissingVertex { vertex_key } if vertex_key == missing
);
}
#[test]
fn test_sorted_facet_points_with_extra_appends_at_end() {
let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let facet_keys = &[keys[0], keys[1]];
let extra = Point::try_new([0.5, 0.5]).expect("finite point coordinates");
let points =
sorted_facet_points_with_extra(&tds, facet_keys, extra).expect("should resolve");
assert_eq!(points.len(), 3);
assert_eq!(points[2], extra);
}
#[test]
fn test_sorted_facet_points_with_extra_permutation_invariant() {
let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let extra = Point::try_new([0.5, 0.5]).expect("finite point coordinates");
let points_a = sorted_facet_points_with_extra(&tds, &[keys[0], keys[1]], extra).unwrap();
let points_b = sorted_facet_points_with_extra(&tds, &[keys[1], keys[0]], extra).unwrap();
assert_eq!(points_a.as_slice(), points_b.as_slice());
}
#[test]
fn test_sorted_facet_points_with_extra_rejects_wrong_arity() {
let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let extra = Point::try_new([0.5, 0.5]).expect("finite point coordinates");
let err = sorted_facet_points_with_extra(&tds, &[keys[0]], extra).unwrap_err();
assert_matches!(
err,
CanonicalFacetPointError::InvalidArity {
expected: 2,
found: 1,
}
);
}
#[test]
fn test_sorted_facet_points_with_extra_reports_missing_vertex_key() {
let (tds, keys) = build_tds_with_points(&[[0.0, 0.0]]);
let missing = VertexKey::from(KeyData::from_ffi(999_999));
let extra = Point::try_new([0.5, 0.5]).expect("finite point coordinates");
let err = sorted_facet_points_with_extra(&tds, &[keys[0], missing], extra).unwrap_err();
assert_matches!(
err,
CanonicalFacetPointError::MissingVertex { vertex_key } if vertex_key == missing
);
}
#[test]
fn test_canonical_insphere_permutation_invariant_2d() {
let (tds, keys) = build_tds_with_points(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let test_point = Point::try_new([1.0, 1.0]).expect("finite point coordinates");
let kernel = AdaptiveKernel::<f64>::new();
let permutations: [[usize; 3]; 6] = [
[0, 1, 2],
[0, 2, 1],
[1, 0, 2],
[1, 2, 0],
[2, 0, 1],
[2, 1, 0],
];
let mut signs = Vec::new();
for perm in &permutations {
let simplex = Simplex::try_new_with_data(
vec![keys[perm[0]], keys[perm[1]], keys[perm[2]]],
None::<()>,
)
.expect("simplex should be valid");
let sorted = sorted_simplex_points(&tds, &simplex).unwrap();
let sign = kernel.in_sphere(&sorted, &test_point).unwrap();
signs.push(sign);
}
assert!(
signs.iter().all(|&s| s == signs[0]),
"canonical sorting must make insphere permutation-invariant: {signs:?}"
);
}
#[test]
fn test_canonical_insphere_permutation_invariant_3d() {
let (tds, keys) = build_tds_with_points(&[
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
]);
let test_point = Point::try_new([1.0, 1.0, 1.0]).expect("finite point coordinates");
let kernel = AdaptiveKernel::<f64>::new();
#[rustfmt::skip]
let perms: [[usize; 4]; 24] = [
[0,1,2,3], [0,1,3,2], [0,2,1,3], [0,2,3,1], [0,3,1,2], [0,3,2,1],
[1,0,2,3], [1,0,3,2], [1,2,0,3], [1,2,3,0], [1,3,0,2], [1,3,2,0],
[2,0,1,3], [2,0,3,1], [2,1,0,3], [2,1,3,0], [2,3,0,1], [2,3,1,0],
[3,0,1,2], [3,0,2,1], [3,1,0,2], [3,1,2,0], [3,2,0,1], [3,2,1,0],
];
let mut signs = Vec::new();
for perm in &perms {
let simplex = Simplex::try_new_with_data(
vec![keys[perm[0]], keys[perm[1]], keys[perm[2]], keys[perm[3]]],
None::<()>,
)
.expect("simplex should be valid");
let sorted = sorted_simplex_points(&tds, &simplex).unwrap();
let sign = kernel.in_sphere(&sorted, &test_point).unwrap();
signs.push(sign);
}
assert!(
signs.iter().all(|&s| s == signs[0]),
"canonical sorting must make 3D insphere permutation-invariant: {signs:?}"
);
}
}