#![forbid(unsafe_code)]
use crate::core::collections::{MAX_PRACTICAL_DIMENSION_SIZE, SmallBuffer};
use crate::core::facet::{FacetError, facet_key_from_vertices};
use crate::core::tds::{SimplexKey, Tds, VertexKey};
use crate::core::util::hashing::stable_hash_u64_slice;
use slotmap::Key;
use thiserror::Error;
pub fn checked_facet_key_from_vertex_keys<const D: usize>(
facet_vertex_keys: &[VertexKey],
) -> Result<u64, FacetError> {
if facet_vertex_keys.len() != D {
return Err(FacetError::InsufficientVertices {
expected: D,
actual: facet_vertex_keys.len(),
dimension: D,
});
}
Ok(facet_key_from_vertices(facet_vertex_keys))
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub(crate) enum PeriodicFacetKeyDerivationError {
#[error("Invalid lifted simplex arity: expected {expected} vertices, got {actual}")]
InvalidLiftedSimplexArity {
expected: usize,
actual: usize,
},
#[error("Facet index {facet_index} out of bounds for lifted vertex count {vertex_count}")]
FacetIndexOutOfBounds {
facet_index: usize,
vertex_count: usize,
},
#[error(
"Periodic offset component {component} (axis {axis}) is out of encodable range 0..=255"
)]
RelativeOffsetOutOfRange {
axis: usize,
component: i16,
},
}
pub(crate) fn periodic_facet_key_from_lifted_vertices<const D: usize>(
lifted_vertices: &[(VertexKey, [i8; D])],
facet_index: usize,
) -> Result<u64, PeriodicFacetKeyDerivationError> {
let expected_arity = D + 1;
if lifted_vertices.len() != expected_arity {
return Err(PeriodicFacetKeyDerivationError::InvalidLiftedSimplexArity {
expected: expected_arity,
actual: lifted_vertices.len(),
});
}
if facet_index >= lifted_vertices.len() {
return Err(PeriodicFacetKeyDerivationError::FacetIndexOutOfBounds {
facet_index,
vertex_count: lifted_vertices.len(),
});
}
let mut lifted_facet: SmallBuffer<(u64, [i8; D]), MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::new();
for (idx, (vertex_key, offset)) in lifted_vertices.iter().enumerate() {
if idx != facet_index {
lifted_facet.push((vertex_key.data().as_ffi(), *offset));
}
}
lifted_facet
.as_mut_slice()
.sort_unstable_by(|(key_a, offset_a), (key_b, offset_b)| {
key_a.cmp(key_b).then_with(|| offset_a.cmp(offset_b))
});
let facet_anchor_offset = lifted_facet
.first()
.map_or([0_i8; D], |(_, offset)| *offset);
let mut packed_signature: SmallBuffer<
u64,
{ MAX_PRACTICAL_DIMENSION_SIZE * MAX_PRACTICAL_DIMENSION_SIZE },
> = SmallBuffer::new();
for (vertex_key_value, offset) in lifted_facet {
packed_signature.push(vertex_key_value);
for axis in 0..D {
let shifted = i16::from(offset[axis]) - i16::from(facet_anchor_offset[axis]) + 128;
if !(0..=255).contains(&shifted) {
return Err(PeriodicFacetKeyDerivationError::RelativeOffsetOutOfRange {
axis,
component: shifted,
});
}
packed_signature
.push(u64::try_from(shifted).expect("validated shifted offset is in 0..=255"));
}
}
Ok(stable_hash_u64_slice(packed_signature.as_slice()))
}
pub fn verify_facet_index_consistency<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex1_key: SimplexKey,
simplex2_key: SimplexKey,
facet_idx: usize,
) -> Result<bool, FacetError> {
let simplex1 = tds
.simplex(simplex1_key)
.ok_or(FacetError::SimplexNotFoundInTriangulation)?;
let simplex2 = tds
.simplex(simplex2_key)
.ok_or(FacetError::SimplexNotFoundInTriangulation)?;
let simplex1_facet_count = simplex1.number_of_vertices();
if facet_idx >= simplex1_facet_count {
return Err(facet_index_error(facet_idx, simplex1_facet_count));
}
let simplex1_key_value = simplex_facet_key::<D>(simplex1.vertices(), facet_idx)?;
for simplex2_facet_idx in 0..simplex2.number_of_vertices() {
if simplex1_key_value == simplex_facet_key::<D>(simplex2.vertices(), simplex2_facet_idx)? {
return Ok(true);
}
}
Ok(false) }
fn simplex_facet_key<const D: usize>(
vertices: &[VertexKey],
omit_idx: usize,
) -> Result<u64, FacetError> {
let expected_vertices = D + 1;
if vertices.len() != expected_vertices {
return Err(FacetError::InsufficientVertices {
expected: expected_vertices,
actual: vertices.len(),
dimension: D,
});
}
if omit_idx >= vertices.len() {
return Err(facet_index_error(omit_idx, vertices.len()));
}
let mut facet_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(vertices.len().saturating_sub(1));
for (idx, &vertex_key) in vertices.iter().enumerate() {
if idx != omit_idx {
facet_vertices.push(vertex_key);
}
}
checked_facet_key_from_vertex_keys::<D>(facet_vertices.as_slice())
}
fn facet_index_error(index: usize, facet_count: usize) -> FacetError {
u8::try_from(index).map_or_else(
|_| FacetError::InvalidFacetIndexOverflow {
original_index: index,
facet_count,
},
|index| FacetError::InvalidFacetIndex { index, facet_count },
)
}
pub fn usize_to_u8(idx: usize, facet_count: usize) -> Result<u8, FacetError> {
u8::try_from(idx).map_err(|_| FacetError::InvalidFacetIndexOverflow {
original_index: idx,
facet_count,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::simplex::Simplex;
use crate::core::util::measure_with_result;
use crate::triangulation::DelaunayTriangulation;
use crate::vertex;
use std::thread;
use std::time::Instant;
#[test]
fn periodic_facet_key_rejects_invalid_lifted_simplex_arity() {
let lifted_vertices = vec![
(VertexKey::null(), [0_i8; 2]),
(VertexKey::null(), [0_i8; 2]),
];
let err = periodic_facet_key_from_lifted_vertices::<2>(&lifted_vertices, 0).unwrap_err();
assert!(matches!(
err,
PeriodicFacetKeyDerivationError::InvalidLiftedSimplexArity {
expected: 3,
actual: 2
}
));
}
#[test]
fn periodic_facet_key_rejects_relative_offset_out_of_range() {
let lifted_vertices = vec![
(VertexKey::null(), [-128_i8, 0_i8]),
(VertexKey::null(), [0_i8, 0_i8]),
(VertexKey::null(), [127_i8, 0_i8]),
];
let err = periodic_facet_key_from_lifted_vertices::<2>(&lifted_vertices, 1).unwrap_err();
assert!(matches!(
err,
PeriodicFacetKeyDerivationError::RelativeOffsetOutOfRange { axis: 0, .. }
));
}
#[test]
fn periodic_facet_key_happy_path_translation_invariance_and_distinctness() {
let lifted_base = vec![
(VertexKey::null(), [0_i8, 0_i8]),
(VertexKey::null(), [1_i8, 0_i8]),
(VertexKey::null(), [0_i8, 1_i8]),
];
let lifted_translated = vec![
(VertexKey::null(), [7_i8, -3_i8]),
(VertexKey::null(), [8_i8, -3_i8]),
(VertexKey::null(), [7_i8, -2_i8]),
];
let lifted_distinct = vec![
(VertexKey::null(), [0_i8, 0_i8]),
(VertexKey::null(), [2_i8, 0_i8]),
(VertexKey::null(), [0_i8, 1_i8]),
];
let base_key: Result<u64, PeriodicFacetKeyDerivationError> =
periodic_facet_key_from_lifted_vertices::<2>(&lifted_base, 0);
let translated_key: Result<u64, PeriodicFacetKeyDerivationError> =
periodic_facet_key_from_lifted_vertices::<2>(&lifted_translated, 0);
let distinct_key: Result<u64, PeriodicFacetKeyDerivationError> =
periodic_facet_key_from_lifted_vertices::<2>(&lifted_distinct, 0);
assert!(base_key.is_ok());
assert!(translated_key.is_ok());
assert!(distinct_key.is_ok());
let base_key = base_key.unwrap();
let translated_key = translated_key.unwrap();
let distinct_key = distinct_key.unwrap();
assert_eq!(
base_key, translated_key,
"uniform global translation should preserve periodic facet key",
);
assert_ne!(
base_key, distinct_key,
"different relative offsets should produce a distinct periodic facet key",
);
}
#[test]
fn periodic_facet_key_rejects_out_of_bounds_facet_index() {
let lifted_vertices = vec![
(VertexKey::null(), [0_i8; 2]),
(VertexKey::null(), [0_i8; 2]),
(VertexKey::null(), [0_i8; 2]),
];
let err = periodic_facet_key_from_lifted_vertices::<2>(&lifted_vertices, 3).unwrap_err();
assert!(matches!(
err,
PeriodicFacetKeyDerivationError::FacetIndexOutOfBounds {
facet_index: 3,
vertex_count: 3
}
));
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "facet-key test keeps related canonicalization cases together"
)]
fn test_checked_facet_key_from_vertex_keys_comprehensive() {
println!("Testing checked_facet_key_from_vertex_keys comprehensively");
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let tds = &dt.as_triangulation().tds;
println!(" Testing basic functionality...");
let simplex = tds.simplices().map(|(_, simplex)| simplex).next().unwrap();
let facet_vertex_keys: Vec<_> = simplex.vertices().iter().skip(1).copied().collect();
let result = checked_facet_key_from_vertex_keys::<3>(&facet_vertex_keys);
assert!(
result.is_ok(),
"Facet key derivation should succeed for valid vertex keys"
);
let facet_key = result.unwrap();
println!(" Derived facet key: {facet_key}");
let result2 = checked_facet_key_from_vertex_keys::<3>(&facet_vertex_keys);
assert!(result2.is_ok(), "Second derivation should also succeed");
assert_eq!(
facet_key,
result2.unwrap(),
"Same vertex keys should produce same facet key"
);
let all_vertex_keys = simplex.vertices();
let different_facet_vertex_keys: Vec<_> = all_vertex_keys.iter().take(3).copied().collect();
if different_facet_vertex_keys.len() == 3
&& different_facet_vertex_keys != facet_vertex_keys
{
let result3 = checked_facet_key_from_vertex_keys::<3>(&different_facet_vertex_keys);
assert!(
result3.is_ok(),
"Different facet key derivation should succeed"
);
let different_facet_key = result3.unwrap();
assert_ne!(
facet_key, different_facet_key,
"Different vertex keys should produce different facet keys"
);
println!(" Different facet key: {different_facet_key}");
}
println!(" Testing error handling...");
let single_key: Vec<VertexKey> = vec![facet_vertex_keys[0]];
let result_count = checked_facet_key_from_vertex_keys::<3>(&single_key);
assert!(
result_count.is_err(),
"Should return error for wrong vertex key count"
);
if let Err(error) = result_count {
match error {
FacetError::InsufficientVertices {
expected,
actual,
dimension,
} => {
assert_eq!(expected, 3, "Expected 3 vertex keys for 3D");
assert_eq!(actual, 1, "Got 1 vertex key");
assert_eq!(dimension, 3, "Dimension should be 3");
}
_ => panic!("Expected InsufficientVertices error, got: {error:?}"),
}
}
let empty_keys: Vec<VertexKey> = vec![];
let result_empty = checked_facet_key_from_vertex_keys::<3>(&empty_keys);
assert!(
result_empty.is_err(),
"Empty vertex keys should fail validation"
);
if let Err(error) = result_empty {
match error {
FacetError::InsufficientVertices {
expected,
actual,
dimension,
} => {
assert_eq!(expected, 3, "Expected 3 vertex keys for 3D");
assert_eq!(actual, 0, "Got 0 vertex keys");
assert_eq!(dimension, 3, "Dimension should be 3");
}
_ => {
panic!(
"Expected InsufficientVertices error for empty vertex keys, got: {error:?}"
)
}
}
}
println!(" Testing consistency with TDS...");
let cache = tds
.build_facet_to_simplices_map()
.expect("Should build facet map in test");
let mut keys_found = 0;
let mut keys_tested = 0;
for simplex in tds.simplices().map(|(_, simplex)| simplex) {
let simplex_vertex_keys = simplex.vertices();
for skip_vertex_idx in 0..simplex_vertex_keys.len() {
let facet_vertex_keys: Vec<_> = simplex_vertex_keys
.iter()
.enumerate()
.filter(|(i, _)| *i != skip_vertex_idx)
.map(|(_, &vk)| vk)
.collect();
if !facet_vertex_keys.is_empty() {
let key_result = checked_facet_key_from_vertex_keys::<3>(&facet_vertex_keys);
if let Ok(derived_key) = key_result {
keys_tested += 1;
if cache.contains_key(&derived_key) {
keys_found += 1;
}
}
}
}
}
println!(" Found {keys_found}/{keys_tested} derived keys in TDS cache");
assert!(keys_tested > 0, "Should have tested some keys");
println!(" ✓ All facet key derivation tests passed");
}
#[test]
fn test_verify_facet_index_consistency_true_false_and_error_cases() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let tds = &dt.as_triangulation().tds;
let simplex_key = tds.simplex_keys().next().unwrap();
assert!(verify_facet_index_consistency(tds, simplex_key, simplex_key, 0).unwrap());
let err = verify_facet_index_consistency(tds, simplex_key, simplex_key, 99).unwrap_err();
assert!(matches!(err, FacetError::InvalidFacetIndex { .. }));
let err_large =
verify_facet_index_consistency(tds, simplex_key, simplex_key, 300).unwrap_err();
println!(" Large facet_idx=300 error: {err_large:?}");
assert!(matches!(
err_large,
FacetError::InvalidFacetIndexOverflow { .. }
));
let mut tds2: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds2
.insert_vertex_with_mapping(vertex!([0.0, 0.0]))
.unwrap();
let v_b = tds2
.insert_vertex_with_mapping(vertex!([1.0, 0.0]))
.unwrap();
let v_c = tds2
.insert_vertex_with_mapping(vertex!([0.0, 1.0]))
.unwrap();
let v_d = tds2
.insert_vertex_with_mapping(vertex!([10.0, 10.0]))
.unwrap();
let v_e = tds2
.insert_vertex_with_mapping(vertex!([11.0, 10.0]))
.unwrap();
let v_f = tds2
.insert_vertex_with_mapping(vertex!([10.0, 11.0]))
.unwrap();
let c1 = tds2
.insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let c2 = tds2
.insert_simplex_with_mapping(Simplex::new(vec![v_d, v_e, v_f], None).unwrap())
.unwrap();
assert!(!verify_facet_index_consistency(&tds2, c1, c2, 0).unwrap());
}
#[test]
fn test_usize_to_u8_conversion_comprehensive() {
assert_eq!(usize_to_u8(0, 4), Ok(0));
assert_eq!(usize_to_u8(1, 4), Ok(1));
assert_eq!(usize_to_u8(255, 256), Ok(255));
assert_eq!(usize_to_u8(u8::MAX as usize, 256), Ok(u8::MAX));
for i in 0u8..=255 {
let result = usize_to_u8(i as usize, 256);
assert_eq!(result, Ok(i), "Failed to convert {i}");
}
assert_eq!(usize_to_u8(254, 300), Ok(254));
assert_eq!(usize_to_u8(255, 300), Ok(255));
assert!(usize_to_u8(256, 300).is_err());
assert!(usize_to_u8(257, 300).is_err());
let facet_counts = [1, 10, 100, 255, 256, 1000, usize::MAX];
for &count in &facet_counts {
assert_eq!(
usize_to_u8(0, count),
Ok(0),
"Valid conversion should succeed"
);
let result_invalid = usize_to_u8(256, count);
assert!(result_invalid.is_err(), "Invalid conversion should fail");
if let Err(FacetError::InvalidFacetIndexOverflow { facet_count, .. }) = result_invalid {
assert_eq!(facet_count, count);
}
}
for i in 0..10 {
assert_eq!(
usize_to_u8(i, 20),
usize_to_u8(i, 20),
"Should be deterministic"
);
}
for i in [256, 1000, usize::MAX] {
assert_eq!(
usize_to_u8(i, 100),
usize_to_u8(i, 100),
"Error cases should be deterministic"
);
}
}
#[test]
fn test_usize_to_u8_error_handling() {
let result = usize_to_u8(256, 10);
assert!(result.is_err());
if let Err(FacetError::InvalidFacetIndexOverflow {
original_index,
facet_count,
}) = result
{
assert_eq!(original_index, 256);
assert_eq!(facet_count, 10);
} else {
panic!("Expected InvalidFacetIndexOverflow error");
}
let result = usize_to_u8(usize::MAX, 5);
assert!(result.is_err());
if let Err(FacetError::InvalidFacetIndexOverflow {
original_index,
facet_count,
}) = result
{
assert_eq!(original_index, usize::MAX);
assert_eq!(facet_count, 5);
} else {
panic!("Expected InvalidFacetIndexOverflow error");
}
let test_cases = [
(256, 100),
(300, 500),
(1000, 1500),
(usize::MAX, 42),
(65536, 10),
];
for &(idx, count) in &test_cases {
let result = usize_to_u8(idx, count);
assert!(result.is_err(), "Should fail for index {idx}");
match result {
Err(FacetError::InvalidFacetIndexOverflow {
original_index,
facet_count,
}) => {
assert_eq!(original_index, idx, "Should preserve original index");
assert_eq!(facet_count, count, "Should preserve facet_count");
}
_ => panic!("Expected InvalidFacetIndex error for index {idx}"),
}
}
let large_values = [257, 1000, 10000, 65536, usize::MAX];
for &val in &large_values {
let result = usize_to_u8(val, val);
assert!(result.is_err(), "Should fail for value {val}");
if let Err(FacetError::InvalidFacetIndexOverflow {
original_index,
facet_count,
}) = result
{
assert_eq!(original_index, val);
assert_eq!(facet_count, val);
}
}
let result = usize_to_u8(300, 42);
assert!(result.is_err());
if let Err(error) = result {
let error_string = format!("{error}");
assert!(
error_string.contains("InvalidFacetIndex") || error_string.contains("index"),
"Error message should indicate invalid index: {error_string}"
);
}
let result = usize_to_u8(usize::MAX, 7);
if let Err(FacetError::InvalidFacetIndexOverflow {
original_index,
facet_count,
}) = result
{
assert_eq!(original_index, usize::MAX);
assert_eq!(facet_count, 7);
} else {
panic!("Expected InvalidFacetIndexOverflow error");
}
}
#[test]
fn test_usize_to_u8_performance_and_threading() {
let start = Instant::now();
for i in 0..1000 {
let _ = usize_to_u8(i % 256, 300);
}
let duration = start.elapsed();
eprintln!("usize_to_u8 valid conversions: 1000 iters in {duration:?}");
let start = Instant::now();
for i in 256..1256 {
let _ = usize_to_u8(i, 100);
}
let duration = start.elapsed();
eprintln!("usize_to_u8 error conversions: 1000 iters in {duration:?}");
let (result, _alloc_info) = measure_with_result(|| {
let mut results = Vec::new();
for i in 0..100 {
results.push(usize_to_u8(i, 200));
}
results
});
for (i, result) in result.iter().enumerate() {
assert_eq!(
*result,
Ok(u8::try_from(i).unwrap()),
"Result should be correct for {i}"
);
}
let handles: Vec<_> = (0..4)
.map(|thread_id| {
thread::spawn(move || {
let mut results = Vec::new();
for i in 0..100 {
let val = (thread_id * 50 + i) % 256;
results.push(usize_to_u8(val, 300));
}
results
})
})
.collect();
for handle in handles {
let results = handle.join().expect("Thread should complete successfully");
for result in results {
assert!(result.is_ok(), "All results should be successful");
}
}
}
}