#![forbid(unsafe_code)]
use std::array::from_fn;
use num_traits::Float;
use super::circumsphere::{
CircumcenterError, CircumcenterFailureReason, DegenerateGeometry, DegenerateMeasure,
};
use super::conversions::{ValueConversionError, safe_usize_to_scalar};
use crate::core::facet::FacetView;
use crate::geometry::matrix::{LaError, LaVector, Matrix, Tolerance, gram_matrix};
use crate::geometry::point::Point;
use crate::geometry::traits::coordinate::CoordinateConversionValue;
use crate::tds::FacetError;
#[derive(Clone, Debug, thiserror::Error, PartialEq)]
#[non_exhaustive]
pub enum SurfaceMeasureError {
#[error("Failed to retrieve facet vertices: {source}")]
FacetVertices {
#[from]
source: FacetError,
},
#[error("Geometry computation failed: {source}")]
GeometryError {
#[source]
source: Box<CircumcenterError>,
},
}
impl From<CircumcenterError> for SurfaceMeasureError {
fn from(source: CircumcenterError) -> Self {
Self::GeometryError {
source: Box::new(source),
}
}
}
const DEGENERACY_EPSILON_FACTOR: f64 = 64.0;
const RELATIVE_GRAM_PIVOT_TOLERANCE: f64 = 1e-12;
fn degeneracy_tolerance(scale: f64) -> f64 {
if scale <= 0.0 {
return 0.0;
}
scale * (DEGENERACY_EPSILON_FACTOR * f64::EPSILON)
}
fn is_zero_or_roundoff(value: f64, scale: f64) -> bool {
let magnitude = Float::abs(value);
magnitude == 0.0 || magnitude <= degeneracy_tolerance(scale)
}
fn ensure_finite_measure_value(
value: f64,
measure: DegenerateMeasure,
) -> Result<(), CircumcenterError> {
if value.is_finite() {
Ok(())
} else {
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NonFiniteMeasure {
measure,
value: CoordinateConversionValue::from_numeric_debug(&value),
},
})
}
}
#[inline]
fn ensure_positive_measure_value(
value: f64,
measure: DegenerateMeasure,
) -> Result<(), CircumcenterError> {
if value.is_finite() && value > 0.0 {
Ok(())
} else {
Err(invalid_measure_error(value, measure))
}
}
#[cold]
fn invalid_measure_error(value: f64, measure: DegenerateMeasure) -> CircumcenterError {
match ensure_finite_measure_value(value, measure) {
Err(error) => error,
Ok(()) => CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NonPositiveSimplexMeasure {
measure,
value: CoordinateConversionValue::from_numeric_debug(&value),
},
},
}
}
fn ensure_finite_measure_values<const N: usize>(
values: &[f64; N],
measure: DegenerateMeasure,
) -> Result<(), CircumcenterError> {
for value in values {
ensure_finite_measure_value(*value, measure)?;
}
Ok(())
}
pub fn simplex_volume<const D: usize>(points: &[Point<D>]) -> Result<f64, CircumcenterError> {
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_UNUSED_IMPORTS").is_some() {
tracing::debug!(
points_len = points.len(),
dimension = D,
"measures::simplex_volume called"
);
}
if points.len() != D + 1 {
return Err(CircumcenterError::InvalidSimplex {
actual: points.len(),
expected: D + 1,
dimension: D,
});
}
match D {
1 => {
let p0 = points[0].coords();
let p1 = points[1].coords();
let diff = [p1[0] - p0[0]];
ensure_finite_measure_values(&diff, DegenerateMeasure::Length)?;
let length = Float::abs(diff[0]);
ensure_finite_measure_value(length, DegenerateMeasure::Length)?;
if length == 0.0 {
return Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateSimplex {
measure: DegenerateMeasure::Length,
degeneracy: DegenerateGeometry::CoincidentPoints,
},
});
}
Ok(length)
}
2 => {
let p0 = points[0].coords();
let p1 = points[1].coords();
let p2 = points[2].coords();
let v1 = [p1[0] - p0[0], p1[1] - p0[1]];
let v2 = [p2[0] - p0[0], p2[1] - p0[1]];
ensure_finite_measure_values(&v1, DegenerateMeasure::Area)?;
ensure_finite_measure_values(&v2, DegenerateMeasure::Area)?;
let cross_z = v1[1].mul_add(-v2[0], v1[0] * v2[1]);
ensure_finite_measure_value(cross_z, DegenerateMeasure::Area)?;
let area = Float::abs(cross_z) / 2.0;
let cross_scale = Float::abs(v1[0] * v2[1]) + Float::abs(v1[1] * v2[0]);
ensure_finite_measure_value(cross_scale, DegenerateMeasure::Area)?;
if is_zero_or_roundoff(cross_z, cross_scale) {
return Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateSimplex {
measure: DegenerateMeasure::Volume,
degeneracy: DegenerateGeometry::CollinearPoints,
},
});
}
ensure_positive_measure_value(area, DegenerateMeasure::Area)?;
Ok(area)
}
3 => {
let p0 = points[0].coords();
let p1 = points[1].coords();
let p2 = points[2].coords();
let p3 = points[3].coords();
let v1 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
let v2 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
let v3 = [p3[0] - p0[0], p3[1] - p0[1], p3[2] - p0[2]];
ensure_finite_measure_values(&v1, DegenerateMeasure::Volume)?;
ensure_finite_measure_values(&v2, DegenerateMeasure::Volume)?;
ensure_finite_measure_values(&v3, DegenerateMeasure::Volume)?;
let cross_x = v2[2].mul_add(-v3[1], v2[1] * v3[2]);
let cross_y = v2[0].mul_add(-v3[2], v2[2] * v3[0]);
let cross_z = v2[1].mul_add(-v3[0], v2[0] * v3[1]);
ensure_finite_measure_values(&[cross_x, cross_y, cross_z], DegenerateMeasure::Volume)?;
let triple_product = v1[2].mul_add(cross_z, v1[1].mul_add(cross_y, v1[0] * cross_x));
ensure_finite_measure_value(triple_product, DegenerateMeasure::Volume)?;
let six = 6.0;
let volume = Float::abs(triple_product) / six;
let triple_scale = Float::abs(v1[0] * v2[1] * v3[2])
+ Float::abs(v1[0] * v2[2] * v3[1])
+ Float::abs(v1[1] * v2[2] * v3[0])
+ Float::abs(v1[1] * v2[0] * v3[2])
+ Float::abs(v1[2] * v2[0] * v3[1])
+ Float::abs(v1[2] * v2[1] * v3[0]);
ensure_finite_measure_value(triple_scale, DegenerateMeasure::Volume)?;
if is_zero_or_roundoff(triple_product, triple_scale) {
return Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateSimplex {
measure: DegenerateMeasure::Volume,
degeneracy: DegenerateGeometry::CoplanarPoints,
},
});
}
ensure_positive_measure_value(volume, DegenerateMeasure::Volume)?;
Ok(volume)
}
_ => {
simplex_volume_gram_matrix::<D>(points)
}
}
}
fn validate_gram_determinant(det: f64) -> Result<f64, CircumcenterError> {
if !det.is_finite() {
return Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NonFiniteGramDeterminant,
});
}
if det < 0.0 {
return Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NegativeGramDeterminant,
});
}
if det == 0.0 {
return Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateSimplex {
measure: DegenerateMeasure::Volume,
degeneracy: DegenerateGeometry::CollinearOrCoplanarPoints,
},
});
}
Ok(det)
}
fn factorial_f64(n: usize) -> Result<f64, CircumcenterError> {
let mut value = 1.0f64;
for k in 2..=n {
let k_f64 = safe_usize_to_scalar(k).map_err(|e| CircumcenterError::ValueConversion {
source: Box::new(ValueConversionError::CoordinateConversion {
value: CoordinateConversionValue::from_usize(k),
from_type: "usize",
to_type: "f64",
source: Box::new(e),
}),
})?;
value *= k_f64;
}
Ok(value)
}
#[inline]
fn gram_determinant_ldlt<const D: usize>(gram_matrix: Matrix<D>) -> Result<f64, CircumcenterError> {
let scale = gram_matrix
.as_rows()
.iter()
.enumerate()
.fold(0.0_f64, |scale, (index, row)| scale.max(row[index]));
let tolerance = Tolerance::try_new(RELATIVE_GRAM_PIVOT_TOLERANCE * scale)?;
let ldlt = gram_matrix.ldlt(tolerance)?;
ldlt.det().map_err(CircumcenterError::from)
}
#[cold]
fn gram_edge_error<const D: usize>(
source: LaError,
coords: &[f64; D],
origin: &[f64; D],
) -> CircumcenterError {
let differences = from_fn(|axis| coords[axis] - origin[axis]);
match ensure_finite_measure_values::<D>(&differences, DegenerateMeasure::Volume) {
Ok(()) => source.into(),
Err(error) => error,
}
}
#[inline]
fn simplex_gram_matrix<const N: usize, const D: usize>(
points: &[Point<D>],
) -> Result<Matrix<N>, CircumcenterError> {
if points.len() != N + 1 {
return Err(CircumcenterError::InvalidSimplex {
actual: points.len(),
expected: N + 1,
dimension: D,
});
}
let origin = points[0].coords();
let mut edges = [LaVector::<D>::zero(); N];
for (edge, point) in edges.iter_mut().zip(&points[1..]) {
let coords = point.coords();
let differences = from_fn(|axis| coords[axis] - origin[axis]);
*edge = LaVector::try_new(differences)
.map_err(|source| gram_edge_error(source, coords, origin))?;
}
gram_matrix(&edges).map_err(CircumcenterError::from)
}
fn simplex_volume_gram_matrix<const D: usize>(
points: &[Point<D>],
) -> Result<f64, CircumcenterError> {
let gram_matrix = simplex_gram_matrix::<D, D>(points)?;
let det = validate_gram_determinant(gram_determinant_ldlt(gram_matrix)?)?;
let volume_f64 = {
let sqrt_det = det.sqrt();
let d_fact = factorial_f64(D)?;
sqrt_det / d_fact
};
ensure_positive_measure_value(volume_f64, DegenerateMeasure::Volume)?;
Ok(volume_f64)
}
pub fn inradius<const D: usize>(points: &[Point<D>]) -> Result<f64, CircumcenterError> {
if points.len() != D + 1 {
return Err(CircumcenterError::InvalidSimplex {
actual: points.len(),
expected: D + 1,
dimension: D,
});
}
if D == 0 {
return Err(CircumcenterError::DimensionTooSmall {
dimension: D,
minimum: 1,
});
}
if D == 1 {
let length = simplex_volume(points)?; let radius = length / 2.0;
ensure_positive_measure_value(radius, DegenerateMeasure::Length)?;
return Ok(radius);
}
let volume = simplex_volume(points)?;
let mut surface_area = 0.0;
for i in 0..=D {
let facet_points: [Point<D>; D] = from_fn(|j| points[j + usize::from(j >= i)]);
let facet_area = facet_measure(&facet_points)?;
surface_area += facet_area;
}
ensure_positive_measure_value(surface_area, DegenerateMeasure::SurfaceArea)?;
let d_scalar = safe_usize_to_scalar(D).map_err(|e| CircumcenterError::ValueConversion {
source: Box::new(ValueConversionError::CoordinateConversion {
value: CoordinateConversionValue::from_usize(D),
from_type: "usize",
to_type: "f64",
source: Box::new(e),
}),
})?;
let inradius = (d_scalar * volume) / surface_area;
ensure_positive_measure_value(inradius, DegenerateMeasure::Length)?;
Ok(inradius)
}
#[expect(
clippy::inline_always,
reason = "measured 2D–6D facet and surface kernels eliminate dimension-dispatch call overhead"
)]
#[inline(always)]
pub fn facet_measure<const D: usize>(points: &[Point<D>]) -> Result<f64, CircumcenterError> {
if points.len() != D {
return Err(CircumcenterError::InvalidSimplex {
actual: points.len(),
expected: D,
dimension: D,
});
}
match D {
0 => Err(CircumcenterError::DimensionTooSmall {
dimension: D,
minimum: 1,
}),
1 => {
Ok(1.0)
}
2 => {
let p0 = points[0].coords();
let p1 = points[1].coords();
let diff = [p1[0] - p0[0], p1[1] - p0[1]];
let length = LaVector::try_new(diff)?.norm()?;
if length == 0.0 {
return Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateFacet {
measure: DegenerateMeasure::Length,
degeneracy: DegenerateGeometry::CoincidentPoints,
},
});
}
Ok(length)
}
3 => {
let p0 = points[0].coords();
let p1 = points[1].coords();
let p2 = points[2].coords();
let v1 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
let v2 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
ensure_finite_measure_values(&v1, DegenerateMeasure::Area)?;
ensure_finite_measure_values(&v2, DegenerateMeasure::Area)?;
let cross = [
v1[2].mul_add(-v2[1], v1[1] * v2[2]),
v1[0].mul_add(-v2[2], v1[2] * v2[0]),
v1[1].mul_add(-v2[0], v1[0] * v2[1]),
];
let cross_magnitude = LaVector::try_new(cross)?.norm()?;
let area = cross_magnitude / (2.0);
let cross_scale = Float::abs(v1[1] * v2[2])
+ Float::abs(v1[2] * v2[1])
+ Float::abs(v1[2] * v2[0])
+ Float::abs(v1[0] * v2[2])
+ Float::abs(v1[0] * v2[1])
+ Float::abs(v1[1] * v2[0]);
ensure_finite_measure_value(cross_scale, DegenerateMeasure::Area)?;
if is_zero_or_roundoff(cross_magnitude, cross_scale) {
return Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateFacet {
measure: DegenerateMeasure::Area,
degeneracy: DegenerateGeometry::CollinearPoints,
},
});
}
ensure_positive_measure_value(area, DegenerateMeasure::Area)?;
Ok(area)
}
4 => {
facet_measure_gram_matrix::<D>(points)
}
_ => {
facet_measure_gram_matrix::<D>(points)
}
}
}
fn facet_measure_gram_matrix<const D: usize>(
points: &[Point<D>],
) -> Result<f64, CircumcenterError> {
let gram_dim = D - 1;
let det = try_with_la_stack_matrix!(gram_dim, |gram_matrix| {
gram_matrix = simplex_gram_matrix(points)?;
validate_gram_determinant(gram_determinant_ldlt(gram_matrix)?)
})?;
let volume_f64 = {
let sqrt_det = det.sqrt();
let d_fact = factorial_f64(D - 1)?;
sqrt_det / d_fact
};
ensure_positive_measure_value(volume_f64, DegenerateMeasure::Volume)?;
Ok(volume_f64)
}
pub fn surface_measure<U, V, const D: usize>(
facets: &[FacetView<'_, U, V, D>],
) -> Result<f64, SurfaceMeasureError> {
let mut total_measure = 0.0;
for facet in facets {
let mut points = [Point::default(); D];
for (point, vertex) in points.iter_mut().zip(facet.vertices()) {
*point = *vertex.point();
}
let measure = facet_measure(&points).map_err(SurfaceMeasureError::from)?;
total_measure += measure;
}
ensure_finite_measure_value(total_measure, DegenerateMeasure::SurfaceArea)?;
Ok(total_measure)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
core::vertex::Vertex, delaunay_model::DelaunayTriangulation,
geometry::traits::coordinate::InvalidCoordinateValue, tds::TdsDraft, vertex,
};
use approx::assert_relative_eq;
use la_stack::ArithmeticOperation;
use std::assert_matches;
fn scaled_axis_simplex<const D: usize>(scales: [f64; D]) -> Vec<Point<D>> {
let mut points = vec![Point::try_new([0.0; D]).unwrap()];
for (axis, scale) in scales.into_iter().enumerate() {
let mut coords = [0.0; D];
coords[axis] = scale;
points.push(Point::try_new(coords).unwrap());
}
points
}
fn check_measure_isometries<const D: usize>() {
let scales = from_fn(|axis| [0.125, 8.0, 0.5, 2.0, 1.0, 1.0][axis]);
let points = scaled_axis_simplex::<D>(scales);
let factorial = (1..=D)
.map(|value| safe_usize_to_scalar(value).unwrap())
.product::<f64>();
let volume = scales.iter().product::<f64>() / factorial;
let facet_volume = volume * safe_usize_to_scalar(D).unwrap() / scales[D - 1];
let inverse_scales = scales.map(f64::recip);
let inradius_expected = (inverse_scales.iter().sum::<f64>()
+ inverse_scales
.iter()
.map(|value| value * value)
.sum::<f64>()
.sqrt())
.recip();
for (rotate, reflect, translation) in [
(false, false, 0.0),
(true, false, 0.0),
(false, true, 0.0),
(false, false, 16.0),
(true, true, 16.0),
] {
let mut transformed: Vec<_> = points
.iter()
.map(|point| {
let mut coords = *point.coords();
if rotate {
let (x, y) = (coords[0], coords[D - 1]);
coords[0] = 0.8_f64.mul_add(-y, 0.6 * x);
coords[D - 1] = 0.6_f64.mul_add(y, 0.8 * x);
}
if reflect {
coords[0] = -coords[0];
}
Point::try_new(coords.map(|value| value + translation)).unwrap()
})
.collect();
assert_relative_eq!(
simplex_volume(&transformed).unwrap(),
volume,
max_relative = 1e-10
);
assert_relative_eq!(
facet_measure(&transformed[..D]).unwrap(),
facet_volume,
max_relative = 1e-10
);
for _ in 0..=D {
assert_relative_eq!(
inradius(&transformed).unwrap(),
inradius_expected,
max_relative = 1e-10
);
transformed.rotate_left(1);
}
}
}
fn check_measure_rescaling<const D: usize>() {
let dimension = safe_usize_to_scalar(D).unwrap();
let exponent = i32::try_from(D).unwrap();
let factorial = (1..=D)
.map(|value| safe_usize_to_scalar(value).unwrap())
.product::<f64>();
for scale_exponent in [-30, 0, 30] {
let side = 2.0_f64.powi(scale_exponent);
let mut points = scaled_axis_simplex([side; D]);
let volume = side.powi(exponent) / factorial;
let facet = side.powi(exponent - 1) / (factorial / dimension);
assert_relative_eq!(
facet_measure(&points[..D]).unwrap(),
facet,
epsilon = 0.0,
max_relative = 1e-12
);
assert_relative_eq!(
facet_measure(&points[1..]).unwrap(),
dimension.sqrt() * facet,
epsilon = 0.0,
max_relative = 1e-12
);
for _ in 0..=D {
assert_relative_eq!(
simplex_volume(&points).unwrap(),
volume,
epsilon = 0.0,
max_relative = 1e-12
);
assert_relative_eq!(
inradius(&points).unwrap(),
side / (dimension + dimension.sqrt()),
epsilon = 0.0,
max_relative = 1e-12
);
points.rotate_left(1);
}
}
}
fn check_gram_rank_cutoff_rescaling<const D: usize>() {
for scale_exponent in [30, 0, -30] {
let side = 2.0_f64.powi(scale_exponent);
let mut scales = [side; D];
scales[0] = side * 2.0_f64.powi(-30);
let points = scaled_axis_simplex(scales);
for result in [simplex_volume(&points), facet_measure(&points[..D])] {
assert_matches!(
result,
Err(CircumcenterError::LinearAlgebraFailure {
source: LaError::Singular { .. }
})
);
}
}
}
fn check_gram_measure_errors<const D: usize>() {
let mut points = scaled_axis_simplex::<D>([1.0; D]);
points[1] = points[0];
for result in [simplex_volume(&points), facet_measure(&points[..D])] {
assert_matches!(
result,
Err(CircumcenterError::LinearAlgebraFailure {
source: LaError::Singular { .. }
})
);
}
for axis in [0, D - 1] {
let mut points = scaled_axis_simplex::<D>([1.0; D]);
let mut edge = [0.0; D];
edge[axis] = 1.0e200;
points[1] = Point::try_new(edge).unwrap();
for result in [simplex_volume(&points), facet_measure(&points[..D])] {
assert_eq!(
result.unwrap_err(),
CircumcenterError::LinearAlgebraFailure {
source: LaError::non_finite_computation_step(
ArithmeticOperation::VectorDotProduct,
axis
)
},
"{D}D dot-product overflow at coordinate {axis}"
);
}
for (origin_value, expected) in [
(-f64::MAX, InvalidCoordinateValue::PositiveInfinity),
(f64::MAX, InvalidCoordinateValue::NegativeInfinity),
] {
let mut points = scaled_axis_simplex::<D>([1.0; D]);
let mut origin = [0.0; D];
origin[axis] = origin_value;
points[0] = Point::try_new(origin).unwrap();
origin[axis] = -origin_value;
points[1] = Point::try_new(origin).unwrap();
for result in [simplex_volume(&points), facet_measure(&points[..D])] {
assert_eq!(
result.unwrap_err(),
CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NonFiniteMeasure {
measure: DegenerateMeasure::Volume,
value: CoordinateConversionValue::NonFinite(expected.clone()),
}
},
"{D}D edge overflow at coordinate {axis} from {origin_value}"
);
}
}
}
}
fn gram_det_from_edges<const N: usize, const AMBIENT: usize>(
edges: &[[f64; AMBIENT]; N],
) -> Result<f64, CircumcenterError> {
let vectors = edges.map(|edge| LaVector::try_new(edge).unwrap());
validate_gram_determinant(gram_determinant_ldlt(gram_matrix(&vectors)?)?)
}
macro_rules! measure_isometry_tests {
($($dim:literal),+) => {
pastey::paste! {
$(#[test]
fn [<measure_isometries_ $dim d>]() {
check_measure_isometries::<$dim>();
}
#[test]
fn [<measure_rescaling_ $dim d>]() {
check_measure_rescaling::<$dim>();
})+
}
};
}
macro_rules! gram_measure_error_tests {
($($dim:literal),+) => {
pastey::paste! {
$(#[test]
fn [<gram_measure_errors_ $dim d>]() {
check_gram_measure_errors::<$dim>();
}
#[test]
fn [<gram_rank_cutoff_rescaling_ $dim d>]() {
check_gram_rank_cutoff_rescaling::<$dim>();
})+
}
};
}
measure_isometry_tests!(2, 3, 4, 5, 6);
gram_measure_error_tests!(4, 5, 6);
#[test]
fn facet_measure_handles_norms_whose_squares_overflow() {
let segment =
[[0.0, 0.0], [3.0e200, 4.0e200]].map(|coords| Point::try_new(coords).unwrap());
assert_relative_eq!(facet_measure(&segment).unwrap() / 1.0e200, 5.0);
let triangle = [[0.0, 0.0, 0.0], [1.0e100, 0.0, 0.0], [0.0, 1.0e100, 0.0]]
.map(|coords| Point::try_new(coords).unwrap());
assert_relative_eq!(facet_measure(&triangle).unwrap() / 1.0e200, 0.5);
}
#[test]
fn simplex_gram_matrix_preserves_exact_symmetry_and_shape() {
let points = [
Point::try_new([2.0, 3.0, 4.0, 5.0]).unwrap(),
Point::try_new([2.25, 3.5, 4.75, 6.0]).unwrap(),
Point::try_new([1.5, 4.0, 6.0, 8.0]).unwrap(),
Point::try_new([3.0, 2.0, 4.5, 7.0]).unwrap(),
Point::try_new([4.0, 3.0, 2.0, 1.0]).unwrap(),
];
let gram = simplex_gram_matrix::<3, 4>(&points[..4]).unwrap();
let expected = [
[1.875, 4.875, 2.125],
[4.875, 14.25, 5.5],
[2.125, 5.5, 6.25],
];
for (i, row) in gram.as_rows().iter().enumerate() {
for (j, value) in row.iter().enumerate() {
assert_relative_eq!(*value, expected[i][j], epsilon = 0.0);
assert_eq!(value.to_bits(), gram.as_rows()[j][i].to_bits());
}
}
for count in [0, 3, 5] {
assert_eq!(
simplex_gram_matrix::<3, 4>(&points[..count]).unwrap_err(),
CircumcenterError::InvalidSimplex {
actual: count,
expected: 4,
dimension: 4,
}
);
}
}
#[test]
fn surface_measure_error_display_names_variants() {
let error = SurfaceMeasureError::from(CircumcenterError::EmptyPointSet);
let display = format!("{error}");
assert!(display.contains("Geometry computation failed"));
assert!(display.contains("Empty point set"));
}
#[test]
fn simplex_volume_zero_dimension_has_unit_measure() {
let point = Point::<0>::try_new([]).expect("finite point coordinates");
assert_relative_eq!(simplex_volume(&[point]).unwrap(), 1.0, epsilon = 1e-12);
}
#[test]
fn test_simplex_volume_1d_line_segment() {
let line = vec![
Point::try_new([0.0]).expect("finite point coordinates"),
Point::try_new([5.0]).expect("finite point coordinates"),
];
let volume = simplex_volume(&line).unwrap();
assert_relative_eq!(volume, 5.0, epsilon = 1e-10);
let line_neg = vec![
Point::try_new([5.0]).expect("finite point coordinates"),
Point::try_new([0.0]).expect("finite point coordinates"),
];
let volume_neg = simplex_volume(&line_neg).unwrap();
assert_relative_eq!(volume_neg, 5.0, epsilon = 1e-10);
}
#[test]
fn simplex_volume_degenerate_segment_errors() {
let line = vec![
Point::try_new([2.0]).expect("finite point coordinates"),
Point::try_new([2.0]).expect("finite point coordinates"),
];
assert_matches!(
simplex_volume(&line),
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateSimplex {
measure: DegenerateMeasure::Length,
degeneracy: DegenerateGeometry::CoincidentPoints,
}
})
);
}
#[test]
fn test_simplex_volume_2d_triangle() {
let triangle = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([3.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 4.0]).expect("finite point coordinates"),
];
let area = simplex_volume(&triangle).unwrap();
assert_relative_eq!(area, 6.0, epsilon = 1e-10);
let equilateral = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.5, 0.866_025]).expect("finite point coordinates"), ];
let area_eq = simplex_volume(&equilateral).unwrap();
assert_relative_eq!(area_eq, 0.433_013, epsilon = 1e-5);
}
#[test]
fn test_simplex_volume_3d_tetrahedron() {
let tetrahedron = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"),
];
let volume = simplex_volume(&tetrahedron).unwrap();
assert_relative_eq!(volume, 1.0 / 6.0, epsilon = 1e-10); }
#[test]
fn test_simplex_volume_4d_simplex() {
let simplex_4d = vec![
Point::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0, 1.0]).expect("finite point coordinates"),
];
let volume = simplex_volume(&simplex_4d).unwrap();
assert_relative_eq!(volume, 1.0 / 24.0, epsilon = 1e-10);
}
#[test]
fn test_simplex_volume_degenerate() {
let collinear = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 1.0]).expect("finite point coordinates"),
Point::try_new([2.0, 2.0]).expect("finite point coordinates"),
];
let result = simplex_volume(&collinear);
assert_matches!(
result,
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateSimplex {
measure: DegenerateMeasure::Volume,
degeneracy: DegenerateGeometry::CollinearPoints,
},
}),
"Degenerate simplex should return a typed collinearity error"
);
}
#[test]
fn simplex_volume_non_finite_1d_intermediate_is_numeric_failure() {
let points = vec![
Point::try_new([-f64::MAX]).expect("finite point coordinates"),
Point::try_new([f64::MAX]).expect("finite point coordinates"),
];
assert_matches!(
simplex_volume(&points),
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NonFiniteMeasure {
measure: DegenerateMeasure::Length,
value: CoordinateConversionValue::NonFinite(
InvalidCoordinateValue::PositiveInfinity
),
},
})
);
}
#[test]
fn simplex_volume_non_finite_2d_intermediate_is_numeric_failure() {
let points = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([f64::MAX, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, f64::MAX]).expect("finite point coordinates"),
];
assert_matches!(
simplex_volume(&points),
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NonFiniteMeasure {
measure: DegenerateMeasure::Area,
value: CoordinateConversionValue::NonFinite(
InvalidCoordinateValue::PositiveInfinity
),
},
})
);
}
#[test]
fn simplex_volume_coplanar_tetrahedron_errors() {
let coplanar = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.25, 0.25, 0.0]).expect("finite point coordinates"),
];
let result = simplex_volume(&coplanar);
assert_matches!(
result,
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateSimplex {
measure: DegenerateMeasure::Volume,
degeneracy: DegenerateGeometry::CoplanarPoints,
},
}),
"coplanar tetrahedron should be a typed degeneracy"
);
}
#[test]
fn test_simplex_volume_very_small_valid_simplices() {
let small_val = 1e-14;
let triangle = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([small_val, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, small_val]).expect("finite point coordinates"),
];
let area = simplex_volume(&triangle).unwrap();
assert_relative_eq!(area, small_val * small_val / 2.0, max_relative = 1e-12);
let tetrahedron = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([small_val, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, small_val, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, small_val]).expect("finite point coordinates"),
];
let volume = simplex_volume(&tetrahedron).unwrap();
assert_relative_eq!(
volume,
small_val * small_val * small_val / 6.0,
max_relative = 1e-12
);
}
#[test]
fn large_area_preserves_scale_aware_degeneracy() {
let side = 2.0_f64.powi(510);
let triangle = scaled_axis_simplex([side, side]);
let embedded_triangle = scaled_axis_simplex([side, side, 1.0]);
for result in [
simplex_volume(&triangle),
facet_measure(&embedded_triangle[..3]),
] {
assert_relative_eq!(result.unwrap(), 2.0_f64.powi(1019), max_relative = 1e-14);
}
assert_relative_eq!(
inradius(&triangle).unwrap(),
side / (2.0 + 2.0_f64.sqrt()),
max_relative = 1e-14
);
}
#[test]
fn large_volume_preserves_scale_aware_degeneracy() {
let side = 2.0_f64.powi(340);
let tetrahedron = scaled_axis_simplex([side; 3]);
assert_relative_eq!(
simplex_volume(&tetrahedron).unwrap(),
2.0_f64.powi(1020) / 6.0,
max_relative = 1e-14
);
assert_relative_eq!(
inradius(&tetrahedron).unwrap(),
side / (3.0 + 3.0_f64.sqrt()),
max_relative = 1e-14
);
}
#[test]
fn large_nearly_collinear_geometry_is_rejected() {
let side = 2.0_f64.powi(510);
let offset = 2.0_f64.powi(463);
let triangle = [
Point::try_new([0.0, 0.0]).unwrap(),
Point::try_new([side, side]).unwrap(),
Point::try_new([side, side + offset]).unwrap(),
];
assert_matches!(
simplex_volume(&triangle),
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateSimplex {
measure: DegenerateMeasure::Volume,
degeneracy: DegenerateGeometry::CollinearPoints,
},
})
);
let embedded = triangle.map(|point| {
let [x, y] = *point.coords();
Point::try_new([x, y, 0.0]).unwrap()
});
assert_matches!(
facet_measure(&embedded),
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateFacet {
measure: DegenerateMeasure::Area,
degeneracy: DegenerateGeometry::CollinearPoints,
},
})
);
}
#[test]
fn area_normalization_rejects_zero_but_accepts_positive_subnormals() {
for bits in [1, 2] {
let height = f64::from_bits(bits);
let triangle = scaled_axis_simplex([1.0, height]);
let embedded_triangle = scaled_axis_simplex([1.0, height, 1.0]);
for result in [
simplex_volume(&triangle),
facet_measure(&embedded_triangle[..3]),
] {
if bits == 1 {
assert_eq!(
result.unwrap_err(),
CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NonPositiveSimplexMeasure {
measure: DegenerateMeasure::Area,
value: CoordinateConversionValue::from_numeric_debug(&0.0),
},
}
);
} else {
assert_eq!(result.unwrap().to_bits(), 1);
}
}
}
}
#[test]
fn volume_normalization_rejects_zero_but_accepts_positive_subnormals() {
for bits in [3, 4] {
let tetrahedron = scaled_axis_simplex([1.0, 1.0, f64::from_bits(bits)]);
let result = simplex_volume(&tetrahedron);
if bits == 3 {
assert_eq!(
result.unwrap_err(),
CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NonPositiveSimplexMeasure {
measure: DegenerateMeasure::Volume,
value: CoordinateConversionValue::from_numeric_debug(&0.0),
},
}
);
} else {
assert_eq!(result.unwrap().to_bits(), 1);
}
}
}
#[test]
fn test_simplex_volume_wrong_point_count() {
let points = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0]).expect("finite point coordinates"),
];
let result = simplex_volume::<2>(&points);
assert!(result.is_err());
}
#[test]
fn inradius_zero_dimension_returns_typed_error() {
let point = Point::<0>::try_new([]).expect("finite point coordinates");
assert_matches!(
inradius(&[point]),
Err(CircumcenterError::DimensionTooSmall {
dimension: 0,
minimum: 1,
})
);
assert_matches!(
inradius::<0>(&[]),
Err(CircumcenterError::InvalidSimplex {
actual: 0,
expected: 1,
dimension: 0,
})
);
}
#[test]
fn inradius_1d_is_half_segment_length() {
let points = [
Point::try_new([-2.0]).expect("finite point coordinates"),
Point::try_new([4.0]).expect("finite point coordinates"),
];
assert_relative_eq!(inradius(&points).unwrap(), 3.0, epsilon = 1e-12);
}
#[test]
fn inradius_rejects_zero_but_accepts_positive_subnormal_radii() {
for bits in [1, 2] {
let height = f64::from_bits(bits);
let segment = scaled_axis_simplex([height]);
let triangle = scaled_axis_simplex([2.0, height]);
assert!(simplex_volume(&segment).unwrap() > 0.0);
assert!(simplex_volume(&triangle).unwrap() > 0.0);
for result in [inradius(&segment), inradius(&triangle)] {
if bits == 1 {
assert_eq!(
result.unwrap_err(),
CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NonPositiveSimplexMeasure {
measure: DegenerateMeasure::Length,
value: CoordinateConversionValue::from_numeric_debug(&0.0),
},
}
);
} else {
assert_eq!(result.unwrap().to_bits(), 1);
}
}
}
}
#[test]
fn inradius_rejects_overflow_when_summing_finite_facets() {
for (base, overflows) in [(8.0e307, false), (1.0e308, true)] {
let triangle = [
Point::try_new([0.0, 0.0]).unwrap(),
Point::try_new([base, 0.0]).unwrap(),
Point::try_new([base / 2.0, 1.0e-3]).unwrap(),
];
let volume = simplex_volume(&triangle).unwrap();
assert!(volume.is_finite() && volume > 0.0);
for [i, j] in [[0, 1], [0, 2], [1, 2]] {
let length = facet_measure(&[triangle[i], triangle[j]]).unwrap();
assert!(length.is_finite() && length > 0.0);
}
if overflows {
assert_matches!(
inradius(&triangle),
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NonFiniteMeasure {
measure: DegenerateMeasure::SurfaceArea,
value: CoordinateConversionValue::NonFinite(
InvalidCoordinateValue::PositiveInfinity
),
},
})
);
} else {
assert_relative_eq!(inradius(&triangle).unwrap(), 5.0e-4, max_relative = 1e-14);
}
}
}
#[test]
fn test_inradius_2d_equilateral_triangle() {
let triangle = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.5, 0.866_025]).expect("finite point coordinates"), ];
let r_in = inradius(&triangle).unwrap();
assert_relative_eq!(r_in, 0.288_675_13, epsilon = 1e-5);
}
#[test]
fn test_inradius_2d_right_triangle() {
let triangle = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([3.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 4.0]).expect("finite point coordinates"),
];
let r_in = inradius(&triangle).unwrap();
assert_relative_eq!(r_in, 1.0, epsilon = 1e-10);
}
#[test]
fn test_inradius_3d_regular_tetrahedron() {
let tetrahedron = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"),
];
let r_in = inradius(&tetrahedron).unwrap();
assert_relative_eq!(r_in, 0.2113, epsilon = 1e-3);
}
#[test]
fn test_inradius_degenerate() {
let collinear = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([2.0, 0.0]).expect("finite point coordinates"),
];
let result = inradius(&collinear);
assert!(result.is_err()); }
#[test]
fn facet_measure_zero_dimension_returns_typed_error() {
assert_matches!(
facet_measure::<0>(&[]),
Err(CircumcenterError::DimensionTooSmall {
dimension: 0,
minimum: 1,
})
);
let point = Point::<0>::try_new([]).expect("finite point coordinates");
assert_matches!(
facet_measure(&[point]),
Err(CircumcenterError::InvalidSimplex {
actual: 1,
expected: 0,
dimension: 0,
})
);
}
#[test]
fn facet_measure_rejects_first_unsupported_gram_dimension() {
let points = scaled_axis_simplex::<9>([1.0; 9]);
assert_matches!(
facet_measure(&points[..9]),
Err(CircumcenterError::UnsupportedMatrixDimension {
requested: 8,
max: 7,
})
);
}
#[test]
fn test_facet_measure_1d_point() {
let points = vec![Point::try_new([5.0]).expect("finite point coordinates")];
let measure = facet_measure(&points).unwrap();
assert_relative_eq!(measure, 1.0, epsilon = 1e-10);
}
#[test]
fn test_facet_measure_2d_line_segment() {
let points = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([3.0, 4.0]).expect("finite point coordinates"),
];
let measure = facet_measure(&points).unwrap();
assert_relative_eq!(measure, 5.0, epsilon = 1e-10);
}
#[test]
fn test_facet_measure_3d_triangle_right_angle() {
let points = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([3.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 4.0, 0.0]).expect("finite point coordinates"),
];
let measure = facet_measure(&points).unwrap();
assert_relative_eq!(measure, 6.0, epsilon = 1e-10);
}
#[test]
fn test_facet_measure_4d_tetrahedron() {
let points = vec![
Point::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite point coordinates"),
];
let measure = facet_measure(&points).unwrap();
assert_relative_eq!(measure, 1.0 / 6.0, epsilon = 1e-10);
}
#[test]
fn test_gram_determinant_ldlt_known_spd() {
let gram = Matrix::<2>::try_from_rows([[4.0, 2.0], [2.0, 3.0]]).unwrap();
let det = gram_determinant_ldlt(gram).unwrap();
assert_relative_eq!(det, 8.0, epsilon = 1e-12);
}
#[test]
fn gram_determinant_ldlt_preserves_determinant_overflow_error() {
let gram = Matrix::<5>::try_from_rows([
[1.0e100, 0.0, 0.0, 0.0, 0.0],
[0.0, 1.0e100, 0.0, 0.0, 0.0],
[0.0, 0.0, 1.0e100, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0e100, 0.0],
[0.0, 0.0, 0.0, 0.0, 1.0e100],
])
.unwrap();
assert_matches!(
gram_determinant_ldlt(gram),
Err(CircumcenterError::LinearAlgebraFailure {
source: LaError::NonFinite { .. }
})
);
}
#[test]
fn test_gram_determinant_parallel_edges_errors() {
let edges = [[1.0, 0.0, 0.0], [2.0, 0.0, 0.0]];
assert_matches!(
gram_det_from_edges(&edges),
Err(CircumcenterError::LinearAlgebraFailure {
source: LaError::Singular { .. }
})
);
}
#[test]
fn degeneracy_tolerance_zero_scale() {
assert_relative_eq!(degeneracy_tolerance(0.0), 0.0);
assert_relative_eq!(degeneracy_tolerance(-1.0), 0.0);
}
#[test]
fn gram_determinant_nonfinite_errors() {
assert_matches!(
validate_gram_determinant(f64::NAN),
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NonFiniteGramDeterminant,
})
);
assert_matches!(
validate_gram_determinant(f64::INFINITY),
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NonFiniteGramDeterminant,
})
);
}
#[test]
fn gram_determinant_zero_reports_degenerate_volume() {
assert_matches!(
validate_gram_determinant(0.0),
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateSimplex {
measure: DegenerateMeasure::Volume,
degeneracy: DegenerateGeometry::CollinearOrCoplanarPoints,
},
})
);
}
#[test]
fn test_validate_gram_determinant_tiny_negative_errors() {
assert_matches!(
validate_gram_determinant(-1e-13),
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NegativeGramDeterminant,
})
);
}
macro_rules! test_gram_det_orthogonal {
($test_name:ident, $dim:literal) => {
#[test]
fn $test_name() {
let mut edges = [[0.0f64; $dim]; $dim];
for i in 0..$dim {
edges[i][i] = 1.0;
}
let det = gram_det_from_edges(&edges).unwrap();
assert_relative_eq!(det, 1.0, epsilon = 1e-10);
}
};
}
test_gram_det_orthogonal!(test_gram_determinant_orthogonal_2d, 2);
test_gram_det_orthogonal!(test_gram_determinant_orthogonal_3d, 3);
test_gram_det_orthogonal!(test_gram_determinant_orthogonal_4d, 4);
test_gram_det_orthogonal!(test_gram_determinant_orthogonal_5d, 5);
macro_rules! test_gram_det_scaled {
($test_name:ident, $dim:literal, $scale:expr, $expected_det:expr) => {
#[test]
fn $test_name() {
let mut edges = [[0.0f64; $dim]; $dim];
for i in 0..$dim {
edges[i][i] = $scale;
}
let det = gram_det_from_edges(&edges).unwrap();
assert_relative_eq!(det, $expected_det, epsilon = 1e-9);
}
};
}
test_gram_det_scaled!(test_gram_determinant_scaled_2d, 2, 2.0, 16.0); test_gram_det_scaled!(test_gram_determinant_scaled_3d, 3, 2.0, 64.0); test_gram_det_scaled!(test_gram_determinant_scaled_4d, 4, 2.0, 256.0); test_gram_det_scaled!(test_gram_determinant_scaled_5d, 5, 2.0, 1024.0);
#[test]
fn test_gram_matrix_debug() {
let triangle_3d = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0]).expect("finite point coordinates"),
];
let area_3d = facet_measure(&triangle_3d).unwrap();
assert_relative_eq!(area_3d, 0.5, epsilon = 1e-10);
#[cfg(feature = "diagnostics")]
tracing::debug!("3D triangle area: {area_3d} (expected: 0.5)");
let eps = 1e-10;
let near_singular = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, eps, 0.0]).expect("finite point coordinates"),
];
let area_ns = facet_measure(&near_singular).unwrap();
assert!(area_ns >= 0.0);
let area_3d_gram = facet_measure_gram_matrix::<3>(&triangle_3d).unwrap();
assert_relative_eq!(area_3d_gram, 0.5, epsilon = 1e-10);
#[cfg(feature = "diagnostics")]
tracing::debug!("3D triangle area (Gram): {area_3d_gram} (expected: 0.5)");
let tetrahedron_4d = vec![
Point::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite point coordinates"),
];
let volume_4d = facet_measure(&tetrahedron_4d).unwrap();
assert_relative_eq!(volume_4d, 1.0 / 6.0, epsilon = 1e-10);
#[cfg(feature = "diagnostics")]
tracing::debug!(
"4D tetrahedron volume: {} (expected: {})",
volume_4d,
1.0 / 6.0
);
let volume_4d_gram = facet_measure_gram_matrix::<4>(&tetrahedron_4d).unwrap();
assert_relative_eq!(volume_4d_gram, 1.0 / 6.0, epsilon = 1e-10);
#[cfg(feature = "diagnostics")]
tracing::debug!(
"4D tetrahedron volume (Gram): {} (expected: {})",
volume_4d_gram,
1.0 / 6.0
);
}
#[test]
fn test_facet_measure_5d_simplex() {
let points = vec![
Point::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0, 1.0, 0.0]).expect("finite point coordinates"),
];
let measure = facet_measure(&points).unwrap();
assert_relative_eq!(measure, 1.0 / 24.0, epsilon = 1e-10);
}
#[test]
fn test_facet_measure_6d_simplex() {
let points = vec![
Point::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0, 1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0]).expect("finite point coordinates"),
];
let measure = facet_measure(&points).unwrap();
assert_relative_eq!(measure, 1.0 / 120.0, epsilon = 1e-10);
}
#[test]
fn test_facet_measure_wrong_point_count() {
let points = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"),
];
let result = facet_measure::<3>(&points);
assert!(result.is_err());
match result.unwrap_err() {
CircumcenterError::InvalidSimplex {
actual,
expected,
dimension,
} => {
assert_eq!(actual, 2);
assert_eq!(expected, 3);
assert_eq!(dimension, 3);
}
other => panic!("Expected InvalidSimplex error, got: {other:?}"),
}
}
#[test]
fn facet_measure_coincident_2d_points_reports_degenerate_length() {
let points = vec![
Point::try_new([1.0, -2.0]).expect("finite point coordinates"),
Point::try_new([1.0, -2.0]).expect("finite point coordinates"),
];
let error = facet_measure(&points).unwrap_err();
assert_matches!(
error,
CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateFacet {
measure: DegenerateMeasure::Length,
degeneracy: DegenerateGeometry::CoincidentPoints,
},
}
);
assert!(error.to_string().contains("Invalid geometric measure"));
}
#[test]
fn facet_measure_non_finite_2d_intermediate_is_numeric_failure() {
let points = vec![
Point::try_new([-f64::MAX, -f64::MAX]).expect("finite point coordinates"),
Point::try_new([f64::MAX, f64::MAX]).expect("finite point coordinates"),
];
assert_eq!(
facet_measure(&points),
Err(CircumcenterError::LinearAlgebraFailure {
source: LaError::non_finite_input_vector(0),
})
);
}
#[test]
fn facet_measure_non_finite_3d_intermediate_is_numeric_failure() {
let points = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([f64::MAX, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, f64::MAX, 0.0]).expect("finite point coordinates"),
];
assert_eq!(
facet_measure(&points),
Err(CircumcenterError::LinearAlgebraFailure {
source: LaError::non_finite_input_vector(2),
})
);
}
#[test]
fn test_facet_measure_zero_area_triangle() {
let points = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([2.0, 0.0, 0.0]).expect("finite point coordinates"), ];
let result = facet_measure(&points);
assert_matches!(
result,
Err(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::DegenerateFacet {
measure: DegenerateMeasure::Area,
degeneracy: DegenerateGeometry::CollinearPoints,
}
})
);
}
#[test]
fn test_facet_measure_nearly_collinear_points_2d() {
let eps = 1e-10;
let points = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, eps]).expect("finite point coordinates"), ];
let measure = facet_measure(&points).unwrap();
let expected = eps.mul_add(eps, 1.0).sqrt(); assert_relative_eq!(measure, expected, epsilon = 1e-9);
}
#[test]
fn test_facet_measure_nearly_coplanar_points_3d() {
let eps = 1e-8;
let points = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, eps, eps]).expect("finite point coordinates"), ];
let measure = facet_measure(&points).unwrap();
assert!(
measure > 0.0,
"Nearly coplanar triangle should have positive area"
);
assert!(
measure < 1e-6,
"Nearly coplanar triangle should have very small area, got: {measure}"
);
}
#[test]
fn test_facet_measure_degenerate_4d_tetrahedron() {
let points = vec![
Point::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.5, 0.5, 0.0, 0.0]).expect("finite point coordinates"),
];
let result = facet_measure(&points);
assert_matches!(
result,
Err(CircumcenterError::LinearAlgebraFailure {
source: LaError::Singular { .. }
})
);
}
#[test]
fn test_surface_measure_empty_facets() {
let facets: Vec<FacetView<'_, (), (), 3>> = vec![];
let result = surface_measure(&facets).unwrap();
assert_relative_eq!(result, 0.0, epsilon = 1e-10);
}
#[test]
fn surface_measure_rejects_overflow_when_summing_finite_facets() {
for (base, overflows) in [(8.0e307, false), (1.0e308, true)] {
let mut draft: TdsDraft<(), (), 2> = TdsDraft::new();
let a = draft.insert_vertex(vertex![0.0, 0.0].unwrap()).unwrap();
let b = draft.insert_vertex(vertex![base, 0.0].unwrap()).unwrap();
let c = draft
.insert_vertex(vertex![base / 2.0, 1.0e-3].unwrap())
.unwrap();
draft.insert_simplex([a, b, c]).unwrap();
let tds = draft.finish().unwrap();
let facets = tds.facets().collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(facets.len(), 3);
for facet in &facets {
let points: Vec<_> = facet.vertices().map(|vertex| *vertex.point()).collect();
let length = facet_measure(&points).unwrap();
assert!(length.is_finite() && length > 0.0);
}
if overflows {
assert_eq!(
surface_measure(&facets).unwrap_err(),
SurfaceMeasureError::from(CircumcenterError::InvalidMeasure {
reason: CircumcenterFailureReason::NonFiniteMeasure {
measure: DegenerateMeasure::SurfaceArea,
value: CoordinateConversionValue::NonFinite(
InvalidCoordinateValue::PositiveInfinity
),
},
})
);
} else {
assert_relative_eq!(
surface_measure(&facets).unwrap(),
1.6e308,
max_relative = 1e-14
);
}
}
}
#[test]
#[expect(
clippy::float_cmp,
reason = "Comparisons are against exact literals (constructed geometry), acceptable in this test"
)]
fn test_surface_measure_single_facet() {
let vertices: Vec<Vertex<(), 3>> = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(), vertex!([3.0, 0.0, 0.0]).unwrap(), vertex!([0.0, 4.0, 0.0]).unwrap(), vertex!([0.0, 0.0, 1.0]).unwrap(), ];
let dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::builder(&vertices).build().unwrap();
let boundary_facets: Vec<_> = dt
.boundary_facets()
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
let tarfacet = boundary_facets
.iter()
.find(|facet| {
let facet_vertices: Vec<_> = facet.vertices().collect();
facet_vertices.len() == 3
&& facet_vertices.iter().any(|v| {
let coords = *v.point().coords();
coords == [0.0, 0.0, 0.0]
})
&& facet_vertices.iter().any(|v| {
let coords = *v.point().coords();
coords == [3.0, 0.0, 0.0]
})
&& facet_vertices.iter().any(|v| {
let coords = *v.point().coords();
coords == [0.0, 4.0, 0.0]
})
})
.expect("Should find the target facet");
let surface_area = surface_measure(std::slice::from_ref(tarfacet)).unwrap();
assert_relative_eq!(surface_area, 6.0, epsilon = 1e-10);
}
#[test]
fn test_surface_measure_consistency_with_facet_measure() {
let vertices: Vec<Vertex<(), 3>> = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(), vertex!([1.0, 0.0, 0.0]).unwrap(), vertex!([0.0, 1.0, 0.0]).unwrap(), vertex!([0.0, 0.0, 1.0]).unwrap(), vertex!([1.0, 1.0, 1.0]).unwrap(), ];
let dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::builder(&vertices).build().unwrap();
let boundary_facets: Vec<_> = dt
.boundary_facets()
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
let facet1 = boundary_facets[0].clone();
let facet2 = boundary_facets[1].clone();
let total_surface = surface_measure(&[facet1.clone(), facet2.clone()]).unwrap();
let points1: Vec<Point<3>> = facet1
.vertices()
.map(|v| {
let coords = *v.point().coords();
Point::try_new(coords).expect("finite point coordinates")
})
.collect();
let points2: Vec<Point<3>> = facet2
.vertices()
.map(|v| {
let coords = *v.point().coords();
Point::try_new(coords).expect("finite point coordinates")
})
.collect();
let measure1 = facet_measure(&points1).unwrap();
let measure2 = facet_measure(&points2).unwrap();
let sum_individual = measure1 + measure2;
assert_relative_eq!(total_surface, sum_individual, epsilon = 1e-10);
}
#[test]
fn test_facet_measure_scaled_simplex_2d() {
let scale = 3.0;
let original_points = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0]).expect("finite point coordinates"),
];
let scaled_points = vec![
Point::try_new([0.0 * scale, 0.0 * scale]).expect("finite point coordinates"),
Point::try_new([1.0 * scale, 0.0 * scale]).expect("finite point coordinates"),
];
let original_measure = facet_measure(&original_points).unwrap();
let scaled_measure = facet_measure(&scaled_points).unwrap();
assert_relative_eq!(scaled_measure, original_measure * scale, epsilon = 1e-10);
}
#[test]
fn test_facet_measure_scaled_simplex_3d() {
let scale = 2.5;
let original_points = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([2.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 3.0, 0.0]).expect("finite point coordinates"),
];
let scaled_points = vec![
Point::try_new([0.0 * scale, 0.0 * scale, 0.0 * scale])
.expect("finite point coordinates"),
Point::try_new([2.0 * scale, 0.0 * scale, 0.0 * scale])
.expect("finite point coordinates"),
Point::try_new([0.0 * scale, 3.0 * scale, 0.0 * scale])
.expect("finite point coordinates"),
];
let original_measure = facet_measure(&original_points).unwrap();
let scaled_measure = facet_measure(&scaled_points).unwrap();
assert_relative_eq!(
scaled_measure,
original_measure * scale * scale,
epsilon = 1e-10
);
}
#[test]
fn test_facet_measure_scaled_simplex_4d() {
let scale = 2.0;
let original_points = vec![
Point::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0, 0.0]).expect("finite point coordinates"),
];
let scaled_points = vec![
Point::try_new([0.0 * scale, 0.0 * scale, 0.0 * scale, 0.0 * scale])
.expect("finite point coordinates"),
Point::try_new([1.0 * scale, 0.0 * scale, 0.0 * scale, 0.0 * scale])
.expect("finite point coordinates"),
Point::try_new([0.0 * scale, 1.0 * scale, 0.0 * scale, 0.0 * scale])
.expect("finite point coordinates"),
Point::try_new([0.0 * scale, 0.0 * scale, 1.0 * scale, 0.0 * scale])
.expect("finite point coordinates"),
];
let original_measure = facet_measure(&original_points).unwrap();
let scaled_measure = facet_measure(&scaled_points).unwrap();
assert_relative_eq!(
scaled_measure,
original_measure * scale.powi(3),
epsilon = 1e-10
);
}
#[test]
fn test_facet_measure_very_large_coordinates() {
let large_val = 1e8;
let points = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([large_val, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, large_val, 0.0]).expect("finite point coordinates"),
];
let result = facet_measure(&points);
assert!(result.is_ok(), "Large coordinates should work");
let measure = result.unwrap();
assert!(measure.is_finite(), "Measure should be finite");
let expected = large_val * large_val / 2.0;
assert_relative_eq!(measure, expected, epsilon = 1e-3);
}
#[test]
fn test_facet_measure_very_small_coordinates() {
let small_val = 1e-14;
let points = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([small_val, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, small_val, 0.0]).expect("finite point coordinates"),
];
let result = facet_measure(&points);
assert!(result.is_ok(), "Small coordinates should work");
let measure = result.unwrap();
assert!(measure.is_finite(), "Measure should be finite");
let expected = small_val * small_val / 2.0;
assert_relative_eq!(measure, expected, epsilon = 1e-40);
}
#[test]
fn test_facet_measure_mixed_positive_negative_coordinates() {
let points = vec![
Point::try_new([-1.0, -1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([2.0, -1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([-1.0, 3.0, 0.0]).expect("finite point coordinates"),
];
let measure = facet_measure(&points).unwrap();
assert_relative_eq!(measure, 6.0, epsilon = 1e-10);
}
#[test]
fn test_facet_measure_translation_invariance() {
let translation = [10.0, 20.0, 30.0];
let original_points = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([3.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 4.0, 0.0]).expect("finite point coordinates"),
];
let translated_points = vec![
Point::try_new([
0.0 + translation[0],
0.0 + translation[1],
0.0 + translation[2],
])
.expect("finite point coordinates"),
Point::try_new([
3.0 + translation[0],
0.0 + translation[1],
0.0 + translation[2],
])
.expect("finite point coordinates"),
Point::try_new([
0.0 + translation[0],
4.0 + translation[1],
0.0 + translation[2],
])
.expect("finite point coordinates"),
];
let original_measure = facet_measure(&original_points).unwrap();
let translated_measure = facet_measure(&translated_points).unwrap();
assert_relative_eq!(original_measure, translated_measure, epsilon = 1e-10);
assert_relative_eq!(original_measure, 6.0, epsilon = 1e-10); }
#[test]
fn test_facet_measure_vertex_permutation_invariance() {
let points_order1 = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([3.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 4.0, 0.0]).expect("finite point coordinates"),
];
let points_order2 = vec![
Point::try_new([3.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 4.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
];
let points_order3 = vec![
Point::try_new([0.0, 4.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([3.0, 0.0, 0.0]).expect("finite point coordinates"),
];
let measure1 = facet_measure(&points_order1).unwrap();
let measure2 = facet_measure(&points_order2).unwrap();
let measure3 = facet_measure(&points_order3).unwrap();
assert_relative_eq!(measure1, measure2, epsilon = 1e-10);
assert_relative_eq!(measure1, measure3, epsilon = 1e-10);
assert_relative_eq!(measure1, 6.0, epsilon = 1e-10);
}
#[test]
fn test_facet_measure_various_triangle_orientations() {
let triangles = [
vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0]).expect("finite point coordinates"),
],
vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"),
],
vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"),
],
vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 1.0]).expect("finite point coordinates"),
],
];
let expected_areas = [0.5, 0.5, 0.5];
for (i, triangle) in triangles.iter().take(3).enumerate() {
let measure = facet_measure(triangle).unwrap();
assert_relative_eq!(measure, expected_areas[i], epsilon = 1e-10);
}
let measure4 = facet_measure(&triangles[3]).unwrap();
assert!(
measure4 > 0.0,
"Diagonal triangle should have positive area"
);
assert!(
measure4.is_finite(),
"Diagonal triangle area should be finite"
);
}
#[test]
#[expect(
clippy::float_cmp,
reason = "Comparisons are against exact literals (constructed geometry), acceptable in this test"
)]
fn test_surface_measure_multiple_facets_different_sizes() {
let vertices1: Vec<Vertex<(), 3>> = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(), vertex!([1.0, 0.0, 0.0]).unwrap(), vertex!([0.0, 1.0, 0.0]).unwrap(), vertex!([0.0, 0.0, 1.0]).unwrap(), ];
let vertices2: Vec<Vertex<(), 3>> = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(), vertex!([6.0, 0.0, 0.0]).unwrap(), vertex!([0.0, 8.0, 0.0]).unwrap(), vertex!([0.0, 0.0, 1.0]).unwrap(), ];
let dt1: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::builder(&vertices1).build().unwrap();
let dt2: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::builder(&vertices2).build().unwrap();
let boundary_facets1: Vec<_> = dt1
.boundary_facets()
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
let small_facet = boundary_facets1
.iter()
.find(|facet| {
let facet_vertices: Vec<_> = facet.vertices().collect();
facet_vertices.len() == 3
&& facet_vertices.iter().any(|v| {
let coords = *v.point().coords();
coords == [0.0, 0.0, 0.0]
})
&& facet_vertices.iter().any(|v| {
let coords = *v.point().coords();
coords == [1.0, 0.0, 0.0]
})
&& facet_vertices.iter().any(|v| {
let coords = *v.point().coords();
coords == [0.0, 1.0, 0.0]
})
})
.expect("Should find small triangle facet")
.clone();
let boundary_facets2: Vec<_> = dt2
.boundary_facets()
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
let large_facet = boundary_facets2
.iter()
.find(|facet| {
let facet_vertices: Vec<_> = facet.vertices().collect();
facet_vertices.len() == 3
&& facet_vertices.iter().any(|v| {
let coords = *v.point().coords();
coords == [0.0, 0.0, 0.0]
})
&& facet_vertices.iter().any(|v| {
let coords = *v.point().coords();
coords == [6.0, 0.0, 0.0]
})
&& facet_vertices.iter().any(|v| {
let coords = *v.point().coords();
coords == [0.0, 8.0, 0.0]
})
})
.expect("Should find large triangle facet")
.clone();
let total_surface = surface_measure(&[small_facet, large_facet]).unwrap();
let expected_total = 0.5 + 24.0;
assert_relative_eq!(total_surface, expected_total, epsilon = 1e-10);
}
#[test]
fn test_surface_measure_2d_perimeter() {
let vertices: Vec<Vertex<(), 2>> = vec![
vertex!([0.0, 0.0]).unwrap(), vertex!([3.0, 0.0]).unwrap(), vertex!([0.0, 4.0]).unwrap(), ];
let dt: DelaunayTriangulation<_, (), (), 2> =
DelaunayTriangulation::builder(&vertices).build().unwrap();
let boundary_facets: Vec<_> = dt
.boundary_facets()
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
let total_perimeter = surface_measure(&boundary_facets).unwrap();
assert_relative_eq!(total_perimeter, 12.0, epsilon = 1e-10);
}
#[test]
fn test_surface_measure_4d_boundary() {
let vertices: Vec<Vertex<(), 4>> = vec![
vertex!([0.0, 0.0, 0.0, 0.0]).unwrap(), vertex!([1.0, 0.0, 0.0, 0.0]).unwrap(), vertex!([0.0, 1.0, 0.0, 0.0]).unwrap(), vertex!([0.0, 0.0, 1.0, 0.0]).unwrap(), vertex!([0.0, 0.0, 0.0, 1.0]).unwrap(), ];
let dt: DelaunayTriangulation<_, (), (), 4> =
DelaunayTriangulation::builder(&vertices).build().unwrap();
let boundary_facets: Vec<_> = dt
.boundary_facets()
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
let total_surface = surface_measure(&boundary_facets).unwrap();
let expected_total = 1.0;
assert_relative_eq!(total_surface, expected_total, epsilon = 1e-10);
}
#[test]
fn test_surface_measure_with_invalid_facet() {
let vertices: Vec<Vertex<(), 3>> = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(), vertex!([1.0, 0.0, 0.0]).unwrap(), vertex!([0.0, 1.0, 0.0]).unwrap(), vertex!([0.0, 0.0, 1.0]).unwrap(), ];
let dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::builder(&vertices).build().unwrap();
let boundary_facets: Vec<_> = dt
.boundary_facets()
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
let result = surface_measure(&boundary_facets[0..1]);
assert!(result.is_ok(), "Valid facets should work");
let area = result.unwrap();
assert!(area > 0.0, "Area should be positive");
assert!(area.is_finite(), "Area should be finite");
}
#[test]
fn test_facet_measure_performance_many_dimensions() {
let points_7d = vec![
Point::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]).expect("finite point coordinates"),
];
let measure_7d = facet_measure(&points_7d).unwrap();
assert_relative_eq!(measure_7d, 1.0 / 720.0, epsilon = 1e-10);
let points_8d = vec![
Point::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
.expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
.expect("finite point coordinates"),
Point::try_new([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
.expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0])
.expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0])
.expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0])
.expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0])
.expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0])
.expect("finite point coordinates"),
];
let measure_8d = facet_measure(&points_8d).unwrap();
assert_relative_eq!(measure_8d, 1.0 / 5040.0, epsilon = 1e-10);
}
#[test]
fn test_surface_measure_many_facets() {
let vertices: Vec<Vertex<(), 3>> = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([2.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 2.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 2.0]).unwrap(),
];
let dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::builder(&vertices).build().unwrap();
let boundary_facets: Vec<_> = dt
.boundary_facets()
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(
boundary_facets.len(),
4,
"Tetrahedron should have 4 boundary facets, got {}",
boundary_facets.len()
);
let total_surface = surface_measure(&boundary_facets).unwrap();
assert!(total_surface.is_finite(), "Total surface should be finite");
assert!(total_surface > 0.0, "Total surface should be positive");
}
#[test]
fn test_facet_measure_equilateral_triangles() {
let side_lengths = [1.0, 2.0, 5.0, 10.0];
for &side in &side_lengths {
let height = side * 3.0_f64.sqrt() / 2.0;
let points = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([side, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([side / 2.0, height, 0.0]).expect("finite point coordinates"),
];
let measure = facet_measure(&points).unwrap();
let expected_area = side * side * 3.0_f64.sqrt() / 4.0;
assert_relative_eq!(measure, expected_area, epsilon = 1e-10);
}
}
#[test]
fn test_facet_measure_regular_tetrahedron_faces() {
let side = 2.0;
let height = side * (2.0_f64 / 3.0).sqrt();
let center_offset = side / (2.0 * 3.0_f64.sqrt());
let v1 = Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates");
let v2 = Point::try_new([side, 0.0, 0.0]).expect("finite point coordinates");
let v3 = Point::try_new([side / 2.0, side * 3.0_f64.sqrt() / 2.0, 0.0])
.expect("finite point coordinates");
let v4 =
Point::try_new([side / 2.0, center_offset, height]).expect("finite point coordinates");
let faces = [
vec![v1, v2, v3], vec![v1, v2, v4], vec![v2, v3, v4], vec![v3, v1, v4], ];
let expected_face_area = side * side * 3.0_f64.sqrt() / 4.0;
for face in &faces {
let measure = facet_measure(face).unwrap();
assert_relative_eq!(measure, expected_face_area, epsilon = 1e-9);
}
}
#[test]
fn test_facet_measure_reflection_invariance() {
let original_points = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([3.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 4.0, 0.0]).expect("finite point coordinates"),
];
let reflections = [
vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([-3.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 4.0, 0.0]).expect("finite point coordinates"),
],
vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([3.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, -4.0, 0.0]).expect("finite point coordinates"),
],
vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([3.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 4.0, 0.0]).expect("finite point coordinates"),
],
];
let original_measure = facet_measure(&original_points).unwrap();
for reflected_points in &reflections {
let reflected_measure = facet_measure(reflected_points).unwrap();
assert_relative_eq!(original_measure, reflected_measure, epsilon = 1e-10);
}
}
#[test]
fn test_facet_measure_rotation_invariance_2d() {
let original_points = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([3.0, 4.0]).expect("finite point coordinates"),
];
let rotated_points = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([-4.0, 3.0]).expect("finite point coordinates"), ];
let original_measure = facet_measure(&original_points).unwrap();
let rotated_measure = facet_measure(&rotated_points).unwrap();
assert_relative_eq!(original_measure, rotated_measure, epsilon = 1e-10);
assert_relative_eq!(original_measure, 5.0, epsilon = 1e-10); }
}