use super::storage::{SimplexUuidSortKey, Tds};
use crate::core::{simplex::Simplex, tds::VertexKey, vertex::Vertex};
use std::cmp::Ordering as CmpOrdering;
type SimplexUuidSortEntry<'a, V, const D: usize> = (SimplexUuidSortKey<D>, &'a Simplex<V, D>);
type SimplexCoordinateSignature<const D: usize> = Vec<[f64; D]>;
fn simplex_uuid_sort_entries<U, V, const D: usize>(
tds: &Tds<U, V, D>,
) -> Option<Vec<SimplexUuidSortEntry<'_, V, D>>> {
tds.simplices
.values()
.map(|simplex| {
let offsets = simplex.periodic_vertex_offsets();
if let Some(offsets) = offsets
&& offsets.len() != simplex.number_of_vertices()
{
return None;
}
let mut ids = SimplexUuidSortKey::<D>::new();
for (idx, &vkey) in simplex.vertices().iter().enumerate() {
let uuid = tds.vertex(vkey).map(Vertex::uuid)?;
let offset = offsets.map_or([0_i8; D], |offsets| offsets[idx]);
ids.push((uuid, offset));
}
ids.sort_unstable();
Some((ids, simplex))
})
.collect()
}
fn compare_coords<const D: usize>(left: &[f64; D], right: &[f64; D]) -> CmpOrdering {
left.iter()
.zip(right)
.find_map(|(left, right)| {
let ordering = left.total_cmp(right);
(ordering != CmpOrdering::Equal).then_some(ordering)
})
.unwrap_or(CmpOrdering::Equal)
}
fn compare_simplex_coordinate_signatures<const D: usize>(
left: &[[f64; D]],
right: &[[f64; D]],
) -> CmpOrdering {
left.iter()
.zip(right)
.find_map(|(left, right)| {
let ordering = compare_coords(left, right);
(ordering != CmpOrdering::Equal).then_some(ordering)
})
.unwrap_or_else(|| left.len().cmp(&right.len()))
}
fn simplex_coordinate_signature<U, V, const D: usize>(
tds: &Tds<U, V, D>,
simplex: &Simplex<V, D>,
) -> Option<SimplexCoordinateSignature<D>> {
let mut coords = simplex
.vertices()
.iter()
.map(|&vkey| tds.vertex(vkey).map(|vertex| *vertex.point().coords()))
.collect::<Option<Vec<_>>>()?;
coords.sort_by(compare_coords);
Some(coords)
}
fn incident_simplex_coordinate_signatures<U, V, const D: usize>(
tds: &Tds<U, V, D>,
vertex_key: VertexKey,
) -> Option<Vec<SimplexCoordinateSignature<D>>> {
let incident_simplices = tds.vertex_to_simplices_index().as_map().get(&vertex_key)?;
let mut signatures = Vec::with_capacity(incident_simplices.len());
for &simplex_key in incident_simplices {
signatures.push(simplex_coordinate_signature(
tds,
tds.simplex(simplex_key)?,
)?);
}
signatures.sort_by(|left, right| compare_simplex_coordinate_signatures(left, right));
Some(signatures)
}
fn vertex_incidence_indices_match<U, V, const D: usize>(
left: &Tds<U, V, D>,
right: &Tds<U, V, D>,
) -> bool {
let mut left_vertices: Vec<_> = left.vertices.iter().collect();
let mut right_vertices: Vec<_> = right.vertices.iter().collect();
left_vertices.sort_by(|(_, left), (_, right)| {
compare_coords(left.point().coords(), right.point().coords())
});
right_vertices.sort_by(|(_, left), (_, right)| {
compare_coords(left.point().coords(), right.point().coords())
});
left_vertices
.iter()
.zip(right_vertices.iter())
.all(|(left_vertex, right_vertex)| {
let left_key = left_vertex.0;
let right_key = right_vertex.0;
incident_simplex_coordinate_signatures(left, left_key)
== incident_simplex_coordinate_signatures(right, right_key)
})
}
impl<U, V, const D: usize> PartialEq for Tds<U, V, D> {
fn eq(&self, other: &Self) -> bool {
if self.vertices.len() != other.vertices.len()
|| self.simplices.len() != other.simplices.len()
|| self.uuid_to_vertex_key.len() != other.uuid_to_vertex_key.len()
|| self.uuid_to_simplex_key.len() != other.uuid_to_simplex_key.len()
{
return false;
}
let mut self_vertices: Vec<_> = self.vertices.values().collect();
let mut other_vertices: Vec<_> = other.vertices.values().collect();
self_vertices.sort_by(|a, b| {
let a_coords = *a.point().coords();
let b_coords = *b.point().coords();
compare_coords(&a_coords, &b_coords)
});
other_vertices.sort_by(|a, b| {
let a_coords = *a.point().coords();
let b_coords = *b.point().coords();
compare_coords(&a_coords, &b_coords)
});
if self_vertices != other_vertices {
return false;
}
let (Some(mut self_simplices), Some(mut other_simplices)) = (
simplex_uuid_sort_entries(self),
simplex_uuid_sort_entries(other),
) else {
return false;
};
self_simplices.sort_by(|(a_ids, _), (b_ids, _)| a_ids.cmp(b_ids));
other_simplices.sort_by(|(a_ids, _), (b_ids, _)| a_ids.cmp(b_ids));
if self_simplices.len() != other_simplices.len() {
return false;
}
for ((_, self_simplex), (_, other_simplex)) in
self_simplices.iter().zip(other_simplices.iter())
{
if !self_simplex.eq_by_vertices(self, other_simplex, other) {
return false;
}
}
if !vertex_incidence_indices_match(self, other) {
return false;
}
true
}
}
#[cfg(test)]
mod tests {
use super::compare_coords;
use crate::DelaunayTriangulation;
use crate::core::collections::PeriodicOffsetBuffer;
use crate::core::tds::VertexKey;
use crate::core::vertex::Vertex;
use crate::vertex;
use slotmap::KeyData;
use std::cmp::Ordering as CmpOrdering;
#[test]
fn compare_coords_uses_total_coordinate_ordering() {
assert_eq!(
compare_coords(&[f64::NAN, 1.0], &[0.0, 1.0]),
CmpOrdering::Greater
);
}
fn standard_vertices<const D: usize>() -> Vec<Vertex<(), D>> {
let mut vertices = Vec::with_capacity(D + 1);
vertices.push(vertex!([0.0; D]).unwrap());
for axis in 0..D {
let mut coords = [0.0; D];
coords[axis] = 1.0;
vertices.push(vertex!(coords).unwrap());
}
vertices
}
fn standard_vertices_with_extra<const D: usize>() -> Vec<Vertex<(), D>> {
let mut vertices = standard_vertices::<D>();
vertices.push(vertex!([0.1; D]).unwrap());
vertices
}
macro_rules! gen_tds_partial_eq_error_path_tests {
($dim:literal) => {
pastey::paste! {
#[test]
fn [<test_tds_partial_eq_different_counts_not_equal_ $dim d>]() {
let verts_a = standard_vertices::<$dim>();
let verts_b = standard_vertices_with_extra::<$dim>();
let dt_a: DelaunayTriangulation<_, (), (), $dim> =
DelaunayTriangulation::builder(&verts_a).build().unwrap();
let dt_b: DelaunayTriangulation<_, (), (), $dim> =
DelaunayTriangulation::builder(&verts_b).build().unwrap();
assert_ne!(dt_a.tds(), dt_b.tds());
}
#[test]
fn [<test_tds_partial_eq_rejects_dangling_simplex_vertex_key_ $dim d>]() {
let verts = standard_vertices::<$dim>();
let dt: DelaunayTriangulation<_, (), (), $dim> =
DelaunayTriangulation::builder(&verts).build().unwrap();
let valid = dt.tds().clone();
let mut corrupted = dt.tds().clone();
let dangling_vertex = VertexKey::from(KeyData::from_ffi(9999));
corrupted.push_first_simplex_vertex_key_storage_only_for_test(dangling_vertex);
assert_ne!(corrupted, valid);
}
#[test]
fn [<test_tds_partial_eq_rejects_misaligned_periodic_offsets_ $dim d>]() {
let verts = standard_vertices::<$dim>();
let dt: DelaunayTriangulation<_, (), (), $dim> =
DelaunayTriangulation::builder(&verts).build().unwrap();
let valid = dt.tds().clone();
let mut corrupted = dt.tds().clone();
corrupted.set_first_simplex_periodic_offsets_storage_only_for_test(Some(
PeriodicOffsetBuffer::new(),
));
assert_ne!(corrupted, valid);
}
#[test]
fn [<test_tds_partial_eq_rejects_missing_vertex_incidence_ $dim d>]() {
let verts = standard_vertices::<$dim>();
let dt: DelaunayTriangulation<_, (), (), $dim> =
DelaunayTriangulation::builder(&verts).build().unwrap();
let valid = dt.tds().clone();
let mut corrupted = dt.tds().clone();
let vertex_key = corrupted.vertices().next().map(|(key, _)| key).unwrap();
corrupted.clear_vertex_incidence_for_test(vertex_key);
assert_ne!(corrupted, valid);
}
}
};
}
gen_tds_partial_eq_error_path_tests!(2);
gen_tds_partial_eq_error_path_tests!(3);
gen_tds_partial_eq_error_path_tests!(4);
gen_tds_partial_eq_error_path_tests!(5);
#[test]
fn test_tds_partial_eq_different_structures_not_equal() {
let verts_a = [
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let verts_b = [
vertex!([0.0, 0.0]).unwrap(),
vertex!([2.0, 0.0]).unwrap(),
vertex!([0.0, 2.0]).unwrap(),
];
let dt_a: DelaunayTriangulation<_, (), (), 2> =
DelaunayTriangulation::builder(&verts_a).build().unwrap();
let dt_b: DelaunayTriangulation<_, (), (), 2> =
DelaunayTriangulation::builder(&verts_b).build().unwrap();
assert_ne!(dt_a.tds(), dt_b.tds());
}
}