#![forbid(unsafe_code)]
use crate::core::cell::CellValidationError;
use crate::geometry::matrix::{matrix_get, matrix_set, matrix_zero_like};
use crate::geometry::point::Point;
use crate::geometry::traits::coordinate::{CoordinateConversionError, CoordinateScalar};
use crate::geometry::util::{
circumcenter, circumradius_with_center, hypot, safe_coords_to_f64, safe_scalar_to_f64,
squared_norm,
};
use crate::prelude::CircumcenterError;
use num_traits::Float;
#[inline]
const fn sign_to_orientation(sign: i8) -> Orientation {
match sign {
1 => Orientation::POSITIVE,
-1 => Orientation::NEGATIVE,
_ => Orientation::DEGENERATE,
}
}
#[inline]
const fn sign_to_insphere(det_sign: i8, orient_sign: i8) -> InSphere {
let effective = det_sign as i16 * orient_sign as i16;
if effective > 0 {
InSphere::INSIDE
} else if effective < 0 {
InSphere::OUTSIDE
} else {
InSphere::BOUNDARY
}
}
#[inline]
pub(crate) fn insphere_from_matrix<const N: usize>(
matrix: &crate::geometry::matrix::Matrix<N>,
k: usize,
orient_sign: i8,
) -> InSphere {
debug_assert_eq!(k, N, "k ({k}) must equal matrix dimension N ({N})");
let det_direct = matrix.det_direct();
if let (Some(det), Some(errbound)) = (det_direct, matrix.det_errbound())
&& det.is_finite()
{
let det_norm = det * f64::from(orient_sign);
if det_norm > errbound {
return InSphere::INSIDE;
}
if det_norm < -errbound {
return InSphere::OUTSIDE;
}
}
let exact_is_safe = det_direct.is_some_and(f64::is_finite)
|| (0..k).all(|i| (0..k).all(|j| matrix.get(i, j).is_some_and(f64::is_finite)));
if exact_is_safe && let Ok(sign) = matrix.det_sign_exact() {
return sign_to_insphere(sign, orient_sign);
}
InSphere::BOUNDARY
}
#[inline]
pub(crate) fn orientation_from_matrix<const N: usize>(
matrix: &crate::geometry::matrix::Matrix<N>,
k: usize,
) -> Orientation {
debug_assert_eq!(k, N, "k ({k}) must equal matrix dimension N ({N})");
let det_direct = matrix.det_direct();
if let (Some(det), Some(errbound)) = (det_direct, matrix.det_errbound())
&& det.is_finite()
{
if det > errbound {
return Orientation::POSITIVE;
}
if det < -errbound {
return Orientation::NEGATIVE;
}
}
let exact_is_safe = det_direct.is_some_and(f64::is_finite)
|| (0..k).all(|i| (0..k).all(|j| matrix.get(i, j).is_some_and(f64::is_finite)));
if exact_is_safe && let Ok(sign) = matrix.det_sign_exact() {
return sign_to_orientation(sign);
}
Orientation::DEGENERATE
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InSphere {
OUTSIDE,
BOUNDARY,
INSIDE,
}
impl std::fmt::Display for InSphere {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::OUTSIDE => write!(f, "OUTSIDE"),
Self::BOUNDARY => write!(f, "BOUNDARY"),
Self::INSIDE => write!(f, "INSIDE"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Orientation {
NEGATIVE,
DEGENERATE,
POSITIVE,
}
impl std::fmt::Display for Orientation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NEGATIVE => write!(f, "NEGATIVE"),
Self::DEGENERATE => write!(f, "DEGENERATE"),
Self::POSITIVE => write!(f, "POSITIVE"),
}
}
}
#[inline]
pub fn simplex_orientation<T, const D: usize>(
simplex_points: &[Point<T, D>],
) -> Result<Orientation, CoordinateConversionError>
where
T: CoordinateScalar,
{
if simplex_points.len() != D + 1 {
return Err(CoordinateConversionError::ConversionFailed {
coordinate_index: 0,
coordinate_value: format!("Expected {} points, got {}", D + 1, simplex_points.len()),
from_type: "point count",
to_type: "valid simplex",
});
}
let k = D + 1;
try_with_la_stack_matrix!(k, |matrix| {
for (i, p) in simplex_points.iter().enumerate() {
let point_coords_f64 = safe_coords_to_f64(p.coords())?;
for (j, &v) in point_coords_f64.iter().enumerate() {
matrix_set(&mut matrix, i, j, v);
}
matrix_set(&mut matrix, i, D, 1.0);
}
Ok(orientation_from_matrix(&matrix, k))
})
}
pub fn insphere_distance<T, const D: usize>(
simplex_points: &[Point<T, D>],
test_point: Point<T, D>,
) -> Result<InSphere, CircumcenterError>
where
T: CoordinateScalar,
{
let circumcenter = circumcenter(simplex_points)?;
let circumradius = circumradius_with_center(simplex_points, &circumcenter)?;
let point_coords = test_point.coords();
let circumcenter_coords = circumcenter.coords();
let mut diff_coords = [T::zero(); D];
for (dst, (p, c)) in diff_coords
.iter_mut()
.zip(point_coords.iter().zip(circumcenter_coords.iter()))
{
*dst = *p - *c;
}
let radius = hypot(&diff_coords);
let base_tolerance = T::default_tolerance();
let scale = Float::max(
T::one(),
Float::max(Float::abs(circumradius), Float::abs(radius)),
);
let tolerance = base_tolerance * scale;
let signed_margin = circumradius - radius;
if Float::abs(signed_margin) <= tolerance {
Ok(InSphere::BOUNDARY)
} else if signed_margin > T::zero() {
Ok(InSphere::INSIDE)
} else {
Ok(InSphere::OUTSIDE)
}
}
#[inline]
pub fn insphere<T, const D: usize>(
simplex_points: &[Point<T, D>],
test_point: Point<T, D>,
) -> Result<InSphere, CoordinateConversionError>
where
T: CoordinateScalar,
{
if simplex_points.len() != D + 1 {
return Err(CoordinateConversionError::ConversionFailed {
coordinate_index: 0,
coordinate_value: format!("Expected {} points, got {}", D + 1, simplex_points.len()),
from_type: "point count",
to_type: "valid simplex",
});
}
if simplex_points.iter().any(|p| p == &test_point) {
return Ok(InSphere::BOUNDARY);
}
let k = D + 2;
try_with_la_stack_matrix!(k, |matrix| {
for (i, p) in simplex_points.iter().enumerate() {
let point_coords = p.coords();
let point_coords_f64 = safe_coords_to_f64(point_coords)?;
for (j, &v) in point_coords_f64.iter().enumerate() {
matrix_set(&mut matrix, i, j, v);
}
let squared_norm_t = squared_norm(point_coords);
matrix_set(&mut matrix, i, D, safe_scalar_to_f64(squared_norm_t)?);
matrix_set(&mut matrix, i, D + 1, 1.0);
}
let test_point_coords = test_point.coords();
let test_point_coords_f64 = safe_coords_to_f64(test_point_coords)?;
for (j, &v) in test_point_coords_f64.iter().enumerate() {
matrix_set(&mut matrix, D + 1, j, v);
}
let test_squared_norm_t = squared_norm(test_point_coords);
matrix_set(
&mut matrix,
D + 1,
D,
safe_scalar_to_f64(test_squared_norm_t)?,
);
matrix_set(&mut matrix, D + 1, D + 1, 1.0);
let mut orient_matrix = matrix_zero_like(&matrix);
for i in 0..=D {
for j in 0..D {
matrix_set(&mut orient_matrix, i, j, matrix_get(&matrix, i, j));
}
matrix_set(&mut orient_matrix, i, D, 1.0);
}
matrix_set(&mut orient_matrix, D + 1, D + 1, 1.0);
let orientation = orientation_from_matrix(&orient_matrix, k);
match orientation {
Orientation::DEGENERATE => Err(CoordinateConversionError::ConversionFailed {
coordinate_index: 0,
coordinate_value: "degenerate simplex".to_string(),
from_type: "simplex",
to_type: "circumsphere containment",
}),
Orientation::POSITIVE | Orientation::NEGATIVE => {
let orient_sign: i8 = if matches!(orientation, Orientation::POSITIVE) {
1
} else {
-1
};
Ok(insphere_from_matrix(&matrix, k, orient_sign))
}
}
})
}
pub fn insphere_lifted<T, const D: usize>(
simplex_points: &[Point<T, D>],
test_point: Point<T, D>,
) -> Result<InSphere, CellValidationError>
where
T: CoordinateScalar,
{
if simplex_points.len() != D + 1 {
return Err(CellValidationError::InsufficientVertices {
actual: simplex_points.len(),
expected: D + 1,
dimension: D,
});
}
let ref_point_coords = simplex_points[0].coords();
let k = D + 1;
try_with_la_stack_matrix!(k, |matrix| {
for (row, point) in simplex_points.iter().skip(1).enumerate() {
let point_coords = point.coords();
let mut relative_coords_t: [T; D] = [T::zero(); D];
for (dst, (p, r)) in relative_coords_t
.iter_mut()
.zip(point_coords.iter().zip(ref_point_coords.iter()))
{
*dst = *p - *r;
}
let relative_coords_f64: [f64; D] = safe_coords_to_f64(&relative_coords_t)
.map_err(|e| CellValidationError::CoordinateConversion { source: e })?;
for (j, &v) in relative_coords_f64.iter().enumerate() {
matrix_set(&mut matrix, row, j, v);
}
let squared_norm_t = squared_norm(&relative_coords_t);
let squared_norm_f64: f64 = safe_scalar_to_f64(squared_norm_t)
.map_err(|e| CellValidationError::CoordinateConversion { source: e })?;
matrix_set(&mut matrix, row, D, squared_norm_f64);
}
let test_point_coords = test_point.coords();
let mut test_relative_coords_t: [T; D] = [T::zero(); D];
for (dst, (p, r)) in test_relative_coords_t
.iter_mut()
.zip(test_point_coords.iter().zip(ref_point_coords.iter()))
{
*dst = *p - *r;
}
let test_relative_coords_f64: [f64; D] = safe_coords_to_f64(&test_relative_coords_t)
.map_err(|e| CellValidationError::CoordinateConversion { source: e })?;
for (j, &v) in test_relative_coords_f64.iter().enumerate() {
matrix_set(&mut matrix, D, j, v);
}
let test_squared_norm_t = squared_norm(&test_relative_coords_t);
let test_squared_norm_f64: f64 = safe_scalar_to_f64(test_squared_norm_t)
.map_err(|e| CellValidationError::CoordinateConversion { source: e })?;
matrix_set(&mut matrix, D, D, test_squared_norm_f64);
let mut orient_matrix = matrix_zero_like(&matrix);
for i in 0..D {
for j in 0..D {
matrix_set(&mut orient_matrix, i, j, matrix_get(&matrix, i, j));
}
}
matrix_set(&mut orient_matrix, D, D, 1.0);
let relative_orientation = orientation_from_matrix(&orient_matrix, k);
match relative_orientation {
Orientation::DEGENERATE => Err(CellValidationError::DegenerateSimplex),
Orientation::POSITIVE | Orientation::NEGATIVE => {
let orient_sign: i8 = if matches!(relative_orientation, Orientation::POSITIVE) {
-1
} else {
1
};
Ok(insphere_from_matrix(&matrix, k, orient_sign))
}
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::traits::coordinate::Coordinate;
use crate::prelude::circumradius;
use approx::assert_relative_eq;
use std::collections::HashMap;
#[test]
fn test_enum_display_and_debug_implementations() {
assert_eq!(format!("{}", InSphere::INSIDE), "INSIDE");
assert_eq!(format!("{}", InSphere::OUTSIDE), "OUTSIDE");
assert_eq!(format!("{}", InSphere::BOUNDARY), "BOUNDARY");
assert_eq!(format!("{:?}", InSphere::INSIDE), "INSIDE");
assert_eq!(format!("{:?}", InSphere::OUTSIDE), "OUTSIDE");
assert_eq!(format!("{:?}", InSphere::BOUNDARY), "BOUNDARY");
assert_eq!(format!("{}", Orientation::POSITIVE), "POSITIVE");
assert_eq!(format!("{}", Orientation::NEGATIVE), "NEGATIVE");
assert_eq!(format!("{}", Orientation::DEGENERATE), "DEGENERATE");
assert_eq!(format!("{:?}", Orientation::POSITIVE), "POSITIVE");
assert_eq!(format!("{:?}", Orientation::NEGATIVE), "NEGATIVE");
assert_eq!(format!("{:?}", Orientation::DEGENERATE), "DEGENERATE");
}
#[test]
fn test_circumradius_2d_to_5d() {
let triangle_2d = vec![
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
];
let radius_2d = circumradius(&triangle_2d).unwrap();
let expected_radius_2d = 2.0_f64.sqrt() / 2.0;
assert_relative_eq!(radius_2d, expected_radius_2d, epsilon = 1e-10);
let tetrahedron_3d = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 1.0]),
];
let radius_3d = circumradius(&tetrahedron_3d).unwrap();
println!("3D circumradius: {radius_3d}");
let expected_radius_3d = (3.0_f64).sqrt() / 2.0;
assert_relative_eq!(radius_3d, expected_radius_3d, epsilon = 1e-10);
let simplex_4d = vec![
Point::new([0.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0]),
];
let radius_4d = circumradius(&simplex_4d).unwrap();
println!("4D circumradius: {radius_4d}");
let expected_radius_4d = 1.0;
assert_relative_eq!(radius_4d, expected_radius_4d, epsilon = 1e-10);
let simplex_5d = vec![
Point::new([0.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 0.0, 1.0]),
];
let radius_5d = circumradius(&simplex_5d).unwrap();
println!("5D circumradius: {radius_5d}");
let expected_radius_5d = (5.0_f64).sqrt() / 2.0;
assert_relative_eq!(radius_5d, expected_radius_5d, epsilon = 1e-10);
assert!(radius_2d > 0.0, "2D radius should be positive");
assert!(radius_3d > 0.0, "3D radius should be positive");
assert!(radius_4d > 0.0, "4D radius should be positive");
assert!(radius_5d > 0.0, "5D radius should be positive");
assert!(
radius_2d < radius_3d,
"Radius should increase from 2D to 3D"
);
assert!(
radius_3d < radius_4d,
"Radius should increase from 3D to 4D"
);
assert!(
radius_4d < radius_5d,
"Radius should increase from 4D to 5D"
);
println!("Circumradius summary:");
let expected_2d = (2.0_f64).sqrt() / 2.0;
let expected_3d = (3.0_f64).sqrt() / 2.0;
let expected_5d = (5.0_f64).sqrt() / 2.0;
println!(" 2D (right triangle): {radius_2d} ≈ {expected_2d:.6}");
println!(" 3D (unit tetrahedron): {radius_3d} ≈ {expected_3d:.6}");
println!(" 4D (unit 4-simplex): {radius_4d} = 1.0");
println!(" 5D (unit 5-simplex): {radius_5d} ≈ {expected_5d:.6}");
}
#[test]
fn test_insphere_basic_functionality_2d_to_5d() {
let simplex_2d = vec![
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
];
assert_eq!(
insphere_lifted(&simplex_2d, Point::new([10.0, 10.0])).unwrap(),
InSphere::OUTSIDE,
"2D far outside point should be OUTSIDE"
);
assert_eq!(
insphere_lifted(&simplex_2d, Point::new([0.1, 0.1])).unwrap(),
InSphere::INSIDE,
"2D inside point should be INSIDE"
);
assert_eq!(
insphere_lifted(&simplex_2d, Point::new([0.0, 0.0])).unwrap(),
InSphere::BOUNDARY,
"2D vertex should be BOUNDARY"
);
let simplex_3d = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 1.0]),
];
assert_eq!(
insphere_lifted(&simplex_3d, Point::new([10.0, 10.0, 10.0])).unwrap(),
InSphere::OUTSIDE,
"3D far outside point should be OUTSIDE"
);
assert_eq!(
insphere_lifted(&simplex_3d, Point::new([0.1, 0.1, 0.1])).unwrap(),
InSphere::INSIDE,
"3D inside point should be INSIDE"
);
assert_eq!(
insphere_lifted(&simplex_3d, Point::new([0.0, 0.0, 0.0])).unwrap(),
InSphere::BOUNDARY,
"3D vertex should be BOUNDARY"
);
let simplex_4d = vec![
Point::new([0.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0]),
];
assert_eq!(
insphere_lifted(&simplex_4d, Point::new([10.0, 10.0, 10.0, 10.0])).unwrap(),
InSphere::OUTSIDE,
"4D far outside point should be OUTSIDE"
);
assert_eq!(
insphere_lifted(&simplex_4d, Point::new([0.1, 0.1, 0.1, 0.1])).unwrap(),
InSphere::INSIDE,
"4D inside point should be INSIDE"
);
assert_eq!(
insphere_lifted(&simplex_4d, Point::new([0.0, 0.0, 0.0, 0.0])).unwrap(),
InSphere::BOUNDARY,
"4D vertex should be BOUNDARY"
);
let simplex_5d = vec![
Point::new([0.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 0.0, 1.0]),
];
assert_eq!(
insphere_lifted(&simplex_5d, Point::new([10.0, 10.0, 10.0, 10.0, 10.0])).unwrap(),
InSphere::OUTSIDE,
"5D far outside point should be OUTSIDE"
);
assert_eq!(
insphere_lifted(&simplex_5d, Point::new([0.1, 0.1, 0.1, 0.1, 0.1])).unwrap(),
InSphere::INSIDE,
"5D inside point should be INSIDE"
);
assert_eq!(
insphere_lifted(&simplex_5d, Point::new([0.0, 0.0, 0.0, 0.0, 0.0])).unwrap(),
InSphere::BOUNDARY,
"5D vertex should be BOUNDARY"
);
}
#[test]
fn test_insphere_edge_cases_and_errors() {
let simplex_1d = vec![Point::new([0.0]), Point::new([2.0])];
let midpoint_1d = Point::new([1.0]);
let far_point_1d = Point::new([10.0]);
assert!(
insphere_lifted(&simplex_1d, midpoint_1d).is_ok(),
"1D midpoint should not error"
);
assert!(
insphere_lifted(&simplex_1d, far_point_1d).is_ok(),
"1D far point should not error"
);
let simplex_3d = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 1.0]),
];
let circumcenter_3d = Point::new([0.5, 0.5, 0.5]);
assert_eq!(
insphere_lifted(&simplex_3d, circumcenter_3d).unwrap(),
InSphere::INSIDE,
"3D circumcenter should be INSIDE"
);
let regular_4d_simplex = vec![
Point::new([1.0, 1.0, 1.0, 1.0]),
Point::new([1.0, -1.0, -1.0, -1.0]),
Point::new([-1.0, 1.0, -1.0, -1.0]),
Point::new([-1.0, -1.0, 1.0, -1.0]),
Point::new([-1.0, -1.0, -1.0, 1.0]),
];
assert_eq!(
insphere_lifted(®ular_4d_simplex, Point::new([0.0, 0.0, 0.0, 0.0])).unwrap(),
InSphere::INSIDE,
"Origin should be inside symmetric 4D simplex"
);
let incomplete_simplex = vec![Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])]; let test_point = Point::new([0.5, 0.5, 0.5]);
assert!(
insphere_lifted(&incomplete_simplex, test_point).is_err(),
"Should error with insufficient vertices"
);
}
#[test]
fn predicates_circumcenter_error_cases() {
let points = vec![Point::new([0.0, 0.0]), Point::new([1.0, 0.0])];
let center_result = circumcenter(&points);
assert!(center_result.is_err());
}
#[test]
fn predicates_circumcenter_collinear_points() {
let points = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([2.0, 0.0, 0.0]),
Point::new([3.0, 0.0, 0.0]),
];
let center_result = circumcenter(&points);
assert!(center_result.is_err());
}
#[test]
fn predicates_circumradius_with_center() {
let points = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 1.0]),
];
let center = circumcenter(&points).unwrap();
let radius_with_center = circumradius_with_center(&points, ¢er);
let radius_direct = circumradius(&points).unwrap();
assert_relative_eq!(radius_with_center.unwrap(), radius_direct, epsilon = 1e-10);
}
#[test]
fn predicates_circumsphere_edge_cases() {
let simplex_points = vec![
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
];
let test_point = Point::new([0.25, 0.25]);
assert!(insphere_distance(&simplex_points, test_point).is_ok());
let far_point = Point::new([100.0, 100.0]);
assert!(insphere_distance(&simplex_points, far_point).is_ok());
}
#[test]
#[expect(clippy::too_many_lines)]
fn test_simplex_orientation_comprehensive() {
let positive_2d = vec![
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
];
assert_eq!(
simplex_orientation(&positive_2d).unwrap(),
Orientation::POSITIVE,
"2D positive orientation failed"
);
let negative_2d = vec![
Point::new([0.0, 0.0]),
Point::new([0.0, 1.0]),
Point::new([1.0, 0.0]),
];
assert_eq!(
simplex_orientation(&negative_2d).unwrap(),
Orientation::NEGATIVE,
"2D negative orientation failed"
);
let degenerate_2d = vec![
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([2.0, 0.0]),
];
assert_eq!(
simplex_orientation(°enerate_2d).unwrap(),
Orientation::DEGENERATE,
"2D degenerate case failed"
);
let positive_3d = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0]),
];
assert_eq!(
simplex_orientation(&positive_3d).unwrap(),
Orientation::POSITIVE,
"3D positive orientation failed"
);
let negative_3d = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 1.0]),
];
assert_eq!(
simplex_orientation(&negative_3d).unwrap(),
Orientation::NEGATIVE,
"3D negative orientation failed"
);
let degenerate_3d = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([1.0, 1.0, 0.0]), ];
assert_eq!(
simplex_orientation(°enerate_3d).unwrap(),
Orientation::DEGENERATE,
"3D degenerate case failed"
);
let positive_4d = vec![
Point::new([0.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0]),
];
assert_eq!(
simplex_orientation(&positive_4d).unwrap(),
Orientation::POSITIVE,
"4D positive orientation failed"
);
let negative_4d = vec![
Point::new([0.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0]),
];
assert_eq!(
simplex_orientation(&negative_4d).unwrap(),
Orientation::NEGATIVE,
"4D negative orientation failed"
);
let degenerate_4d = vec![
Point::new([0.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0]),
Point::new([1.0, 1.0, 1.0, 0.0]), ];
assert_eq!(
simplex_orientation(°enerate_4d).unwrap(),
Orientation::DEGENERATE,
"4D degenerate case failed"
);
let positive_5d = vec![
Point::new([0.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 0.0, 1.0]),
];
assert_eq!(
simplex_orientation(&positive_5d).unwrap(),
Orientation::POSITIVE,
"5D positive orientation failed"
);
let negative_5d = vec![
Point::new([0.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 0.0, 1.0]),
];
assert_eq!(
simplex_orientation(&negative_5d).unwrap(),
Orientation::NEGATIVE,
"5D negative orientation failed"
);
let degenerate_5d = vec![
Point::new([0.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0, 0.0]),
Point::new([1.0, 1.0, 1.0, 1.0, 0.0]), ];
assert_eq!(
simplex_orientation(°enerate_5d).unwrap(),
Orientation::DEGENERATE,
"5D degenerate case failed"
);
let insufficient_vertices = vec![Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])]; assert!(
simplex_orientation(&insufficient_vertices).is_err(),
"Should error with insufficient vertices"
);
}
#[test]
fn test_insphere_degenerate_simplex_error_handling() {
let degenerate_simplex = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([1.0, 1.0, 0.0]), ];
let test_point = Point::new([0.5, 0.5, 0.5]);
let result = insphere(°enerate_simplex, test_point);
assert!(
result.is_err(),
"insphere should error with degenerate simplex"
);
if let Err(err) = result {
let err_str = err.to_string();
assert!(
err_str.contains("degenerate"),
"Error should mention degeneracy: {err_str}"
);
}
let result_lifted = insphere_lifted(°enerate_simplex, test_point);
assert!(
result_lifted.is_err(),
"insphere_lifted should error with degenerate simplex"
);
match result_lifted {
Err(CellValidationError::DegenerateSimplex) => (), Err(other) => panic!("Wrong error type: {other:?}"),
Ok(_) => panic!("Function should have returned an error"),
}
let insufficient_vertices = vec![Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])];
assert!(
insphere_distance(&insufficient_vertices, test_point).is_err(),
"insphere_distance should error with insufficient vertices"
);
}
#[test]
fn test_insphere_lifted_edge_case_boundary() {
let simplex_points = vec![
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
];
let vertex_point = Point::new([0.0, 0.0]);
let result = insphere_lifted(&simplex_points, vertex_point).unwrap();
assert_eq!(
result,
InSphere::BOUNDARY,
"Original vertex should be classified as BOUNDARY"
);
let inside_point = Point::new([0.1, 0.1]);
let inside_result = insphere_lifted(&simplex_points, inside_point).unwrap();
assert_eq!(
inside_result,
InSphere::INSIDE,
"Point inside should be classified as INSIDE"
);
let outside_point = Point::new([10.0, 10.0]);
let outside_result = insphere_lifted(&simplex_points, outside_point).unwrap();
assert_eq!(
outside_result,
InSphere::OUTSIDE,
"Point outside should be classified as OUTSIDE"
);
}
#[test]
fn test_insphere_and_insphere_lifted_consistency() {
let simplex_points = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 1.0]),
];
let test_cases = [
(Point::new([0.2, 0.2, 0.2]), InSphere::INSIDE),
(Point::new([2.0, 2.0, 2.0]), InSphere::OUTSIDE),
(Point::new([0.0, 0.0, 0.0]), InSphere::BOUNDARY),
];
for (point, expected) in &test_cases {
let result1 = insphere(&simplex_points, *point).unwrap();
let result2 = insphere_lifted(&simplex_points, *point).unwrap();
if *expected == InSphere::BOUNDARY {
assert!(
result1 == InSphere::BOUNDARY || result2 == InSphere::BOUNDARY,
"Point {point:?} should be classified as BOUNDARY by at least one method"
);
} else {
assert_eq!(result1, *expected, "insphere result mismatch for {point:?}");
assert_eq!(
result2, *expected,
"insphere_lifted result mismatch for {point:?}"
);
}
}
}
#[test]
fn test_insphere_methods_2d_comprehensive() {
let simplex = vec![
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
];
let test_cases = [
(Point::new([0.1, 0.1]), "inside"), (Point::new([10.0, 10.0]), "outside"), (Point::new([0.0, 0.0]), "boundary"), (Point::new([0.5, 0.0]), "boundary"), ];
for (test_point, description) in &test_cases {
let result_std = insphere(&simplex, *test_point).unwrap();
let result_lifted = insphere_lifted(&simplex, *test_point).unwrap();
let result_distance = insphere_distance(&simplex, *test_point).unwrap();
println!(
"2D {description}: std={result_std:?}, lifted={result_lifted:?}, distance={result_distance:?}"
);
if *description != "boundary" {
assert_eq!(
result_std, result_distance,
"2D {description}: std vs distance mismatch"
);
}
}
}
#[test]
fn test_insphere_methods_3d_comprehensive() {
let simplex = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 1.0]),
];
let test_cases = [
(Point::new([0.1, 0.1, 0.1]), "inside"),
(Point::new([10.0, 10.0, 10.0]), "outside"),
(Point::new([0.0, 0.0, 0.0]), "boundary"), (Point::new([0.25, 0.25, 0.25]), "inside"), ];
for (test_point, description) in &test_cases {
let result_std = insphere(&simplex, *test_point).unwrap();
let result_lifted = insphere_lifted(&simplex, *test_point).unwrap();
let result_distance = insphere_distance(&simplex, *test_point).unwrap();
println!(
"3D {description}: std={result_std:?}, lifted={result_lifted:?}, distance={result_distance:?}"
);
if *description != "boundary" {
assert_eq!(
result_std, result_lifted,
"3D {description}: std vs lifted mismatch"
);
assert_eq!(
result_std, result_distance,
"3D {description}: std vs distance mismatch"
);
}
}
}
#[test]
fn test_insphere_methods_4d_comprehensive() {
let simplex = vec![
Point::new([0.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0]),
];
let test_cases = [
(Point::new([0.1, 0.1, 0.1, 0.1]), "inside"),
(Point::new([10.0, 10.0, 10.0, 10.0]), "outside"),
(Point::new([0.0, 0.0, 0.0, 0.0]), "boundary"), (Point::new([0.2, 0.2, 0.2, 0.2]), "inside"),
];
for (test_point, description) in &test_cases {
let result_std = insphere(&simplex, *test_point).unwrap();
let result_lifted = insphere_lifted(&simplex, *test_point).unwrap();
let result_distance = insphere_distance(&simplex, *test_point).unwrap();
println!(
"4D {description}: std={result_std:?}, lifted={result_lifted:?}, distance={result_distance:?}"
);
if *description != "boundary" {
assert_eq!(
result_std, result_lifted,
"4D {description}: std vs lifted mismatch"
);
assert_eq!(
result_std, result_distance,
"4D {description}: std vs distance mismatch"
);
}
}
}
#[test]
fn test_insphere_methods_5d_comprehensive() {
let simplex = vec![
Point::new([0.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 0.0, 1.0]),
];
let test_cases = [
(Point::new([0.1, 0.1, 0.1, 0.1, 0.1]), "inside"),
(Point::new([10.0, 10.0, 10.0, 10.0, 10.0]), "outside"),
(Point::new([0.0, 0.0, 0.0, 0.0, 0.0]), "boundary"), (Point::new([0.15, 0.15, 0.15, 0.15, 0.15]), "inside"),
];
for (test_point, description) in &test_cases {
let result_std = insphere(&simplex, *test_point).unwrap();
let result_lifted = insphere_lifted(&simplex, *test_point).unwrap();
let result_distance = insphere_distance(&simplex, *test_point).unwrap();
println!(
"5D {description}: std={result_std:?}, lifted={result_lifted:?}, distance={result_distance:?}"
);
if *description != "boundary" {
assert_eq!(
result_std, result_lifted,
"5D {description}: std vs lifted mismatch"
);
assert_eq!(
result_std, result_distance,
"5D {description}: std vs distance mismatch"
);
}
}
}
#[test]
fn test_edge_cases_across_dimensions() {
let tiny_simplex_2d = vec![
Point::new([0.0, 0.0]),
Point::new([1e-6, 0.0]),
Point::new([0.0, 1e-6]),
];
let test_point_2d = Point::new([1e-7, 1e-7]);
let result_2d = insphere(&tiny_simplex_2d, test_point_2d);
assert!(result_2d.is_ok(), "2D tiny simplex should work");
let large_simplex_3d = vec![
Point::new([1e6, 0.0, 0.0]),
Point::new([1e6 + 1.0, 0.0, 0.0]),
Point::new([1e6, 1.0, 0.0]),
Point::new([1e6, 0.0, 1.0]),
];
let test_point_3d = Point::new([1e6 + 0.1, 0.1, 0.1]);
let result_3d = insphere(&large_simplex_3d, test_point_3d);
assert!(result_3d.is_ok(), "3D large coordinates should work");
let negative_simplex_4d = vec![
Point::new([-1.0, -1.0, -1.0, -1.0]),
Point::new([0.0, -1.0, -1.0, -1.0]),
Point::new([-1.0, 0.0, -1.0, -1.0]),
Point::new([-1.0, -1.0, 0.0, -1.0]),
Point::new([-1.0, -1.0, -1.0, 0.0]),
];
let test_point_4d = Point::new([-0.5, -0.5, -0.5, -0.5]);
let result_4d = insphere(&negative_simplex_4d, test_point_4d);
assert!(result_4d.is_ok(), "4D negative coordinates should work");
}
#[test]
fn test_method_consistency_stress_test() {
let mut disagreement_count = HashMap::new();
let mut total_tests = 0;
let simplex_3d = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 1.0]),
];
let test_points = [
Point::new([0.1, 0.1, 0.1]),
Point::new([0.3, 0.2, 0.1]),
Point::new([0.5, 0.5, 0.5]),
Point::new([1.0, 1.0, 1.0]),
Point::new([2.0, 2.0, 2.0]),
Point::new([-0.1, -0.1, -0.1]),
Point::new([0.25, 0.25, 0.25]),
Point::new([0.01, 0.01, 0.01]),
];
for test_point in &test_points {
total_tests += 1;
let result_std = insphere(&simplex_3d, *test_point).unwrap();
let result_lifted = insphere_lifted(&simplex_3d, *test_point).unwrap();
let result_distance = insphere_distance(&simplex_3d, *test_point).unwrap();
if result_std != result_lifted {
*disagreement_count.entry("std_vs_lifted").or_insert(0) += 1;
}
if result_std != result_distance {
*disagreement_count.entry("std_vs_distance").or_insert(0) += 1;
}
if result_lifted != result_distance {
*disagreement_count.entry("lifted_vs_distance").or_insert(0) += 1;
}
}
println!("Stress test results: {total_tests} total tests");
for (key, count) in &disagreement_count {
println!(" {key}: {count} disagreements");
}
assert_eq!(
disagreement_count.len(),
0,
"All methods should agree after sign fix"
);
}
fn check_insphere_parity<T, const D: usize>(
simplex: &[Point<T, D>],
test_point: Point<T, D>,
expected_orientation: Orientation,
expected_result: InSphere,
dimension: usize,
orientation_label: &str,
) where
T: CoordinateScalar,
{
let orientation = simplex_orientation(simplex).unwrap();
assert_eq!(
orientation, expected_orientation,
"{dimension}D simplex should be {orientation_label}"
);
let result_lifted = insphere_lifted(simplex, test_point).unwrap();
let result_std = insphere(simplex, test_point).unwrap();
assert_eq!(
result_lifted, result_std,
"{dimension}D {orientation_label}: insphere_lifted should match insphere"
);
assert_eq!(
result_lifted, expected_result,
"{dimension}D {orientation_label}: test point should be {expected_result:?}"
);
}
#[test]
fn test_insphere_lifted_parity_branch_positive_orientation() {
check_insphere_parity(
&[
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
],
Point::new([0.1, 0.1]),
Orientation::POSITIVE,
InSphere::INSIDE,
2,
"POSITIVE",
);
check_insphere_parity(
&[
Point::new([0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0]),
],
Point::new([0.1, 0.1, 0.1]),
Orientation::POSITIVE,
InSphere::INSIDE,
3,
"POSITIVE",
);
check_insphere_parity(
&[
Point::new([0.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0]),
],
Point::new([0.1, 0.1, 0.1, 0.1]),
Orientation::POSITIVE,
InSphere::INSIDE,
4,
"POSITIVE",
);
check_insphere_parity(
&[
Point::new([0.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 0.0, 1.0]),
],
Point::new([0.1, 0.1, 0.1, 0.1, 0.1]),
Orientation::POSITIVE,
InSphere::INSIDE,
5,
"POSITIVE",
);
}
#[test]
fn test_insphere_lifted_parity_branch_negative_orientation() {
check_insphere_parity(
&[
Point::new([0.0, 0.0]),
Point::new([0.0, 1.0]),
Point::new([1.0, 0.0]),
],
Point::new([0.1, 0.1]),
Orientation::NEGATIVE,
InSphere::INSIDE,
2,
"NEGATIVE",
);
check_insphere_parity(
&[
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 1.0]),
],
Point::new([0.1, 0.1, 0.1]),
Orientation::NEGATIVE,
InSphere::INSIDE,
3,
"NEGATIVE",
);
check_insphere_parity(
&[
Point::new([0.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0]),
],
Point::new([0.1, 0.1, 0.1, 0.1]),
Orientation::NEGATIVE,
InSphere::INSIDE,
4,
"NEGATIVE",
);
check_insphere_parity(
&[
Point::new([0.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 1.0, 0.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 0.0, 0.0, 1.0]),
],
Point::new([0.1, 0.1, 0.1, 0.1, 0.1]),
Orientation::NEGATIVE,
InSphere::INSIDE,
5,
"NEGATIVE",
);
}
#[test]
fn test_orientation_from_matrix_positive() {
let k = 3;
with_la_stack_matrix!(k, |m| {
matrix_set(&mut m, 0, 0, 0.0);
matrix_set(&mut m, 0, 1, 0.0);
matrix_set(&mut m, 0, 2, 1.0);
matrix_set(&mut m, 1, 0, 1.0);
matrix_set(&mut m, 1, 1, 0.0);
matrix_set(&mut m, 1, 2, 1.0);
matrix_set(&mut m, 2, 0, 0.0);
matrix_set(&mut m, 2, 1, 1.0);
matrix_set(&mut m, 2, 2, 1.0);
assert_eq!(orientation_from_matrix(&m, k), Orientation::POSITIVE);
});
}
#[test]
fn test_orientation_from_matrix_negative() {
let k = 3;
with_la_stack_matrix!(k, |m| {
matrix_set(&mut m, 0, 0, 0.0);
matrix_set(&mut m, 0, 1, 1.0);
matrix_set(&mut m, 0, 2, 1.0);
matrix_set(&mut m, 1, 0, 1.0);
matrix_set(&mut m, 1, 1, 0.0);
matrix_set(&mut m, 1, 2, 1.0);
matrix_set(&mut m, 2, 0, 0.0);
matrix_set(&mut m, 2, 1, 0.0);
matrix_set(&mut m, 2, 2, 1.0);
assert_eq!(orientation_from_matrix(&m, k), Orientation::NEGATIVE);
});
}
#[test]
fn test_orientation_from_matrix_degenerate() {
let k = 3;
with_la_stack_matrix!(k, |m| {
matrix_set(&mut m, 0, 0, 0.0);
matrix_set(&mut m, 0, 1, 0.0);
matrix_set(&mut m, 0, 2, 1.0);
matrix_set(&mut m, 1, 0, 1.0);
matrix_set(&mut m, 1, 1, 0.0);
matrix_set(&mut m, 1, 2, 1.0);
matrix_set(&mut m, 2, 0, 2.0);
matrix_set(&mut m, 2, 1, 0.0);
matrix_set(&mut m, 2, 2, 1.0);
assert_eq!(orientation_from_matrix(&m, k), Orientation::DEGENERATE);
});
}
#[test]
fn test_orientation_from_matrix_extreme_magnitude_fallback() {
let k = 3;
let big = f64::MAX / 2.0;
with_la_stack_matrix!(k, |m| {
matrix_set(&mut m, 0, 0, 0.0);
matrix_set(&mut m, 0, 1, 0.0);
matrix_set(&mut m, 0, 2, 1.0);
matrix_set(&mut m, 1, 0, big);
matrix_set(&mut m, 1, 1, 0.0);
matrix_set(&mut m, 1, 2, 1.0);
matrix_set(&mut m, 2, 0, 0.0);
matrix_set(&mut m, 2, 1, big);
matrix_set(&mut m, 2, 2, 1.0);
let result = orientation_from_matrix(&m, k);
assert_eq!(
result,
Orientation::POSITIVE,
"Extreme-magnitude fallback should still resolve correct orientation"
);
});
}
#[test]
fn test_orientation_from_matrix_nonfinite_entry_falls_to_stage3() {
let k = 4;
with_la_stack_matrix!(k, |m| {
matrix_set(&mut m, 0, 0, 0.0);
matrix_set(&mut m, 0, 1, 0.0);
matrix_set(&mut m, 0, 2, 1.0);
matrix_set(&mut m, 1, 0, 1.0);
matrix_set(&mut m, 1, 1, 0.0);
matrix_set(&mut m, 1, 2, 1.0);
matrix_set(&mut m, 2, 0, 0.0);
matrix_set(&mut m, 2, 1, 1.0);
matrix_set(&mut m, 2, 2, 1.0);
matrix_set(&mut m, 3, 3, f64::NAN);
let result = orientation_from_matrix(&m, k);
assert_eq!(
result,
Orientation::DEGENERATE,
"non-finite entry in matrix should fall to Stage 3 → DEGENERATE"
);
});
}
#[test]
fn test_exact_orientation_near_degenerate_2d() {
let eps = f64::from_bits(0x3CD0_0000_0000_0000); let nearly_collinear = vec![
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.5, eps]),
];
let orientation = simplex_orientation(&nearly_collinear).unwrap();
assert_eq!(
orientation,
Orientation::POSITIVE,
"Near-degenerate 2D triangle with 2^-50 perturbation should be POSITIVE"
);
}
#[test]
fn test_exact_orientation_near_degenerate_3d() {
let eps = f64::from_bits(0x3CD0_0000_0000_0000); let nearly_coplanar = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, eps]),
];
let orientation = simplex_orientation(&nearly_coplanar).unwrap();
assert_ne!(
orientation,
Orientation::DEGENERATE,
"Near-degenerate 3D tetrahedron with 2^-50 perturbation should NOT be DEGENERATE"
);
}
#[test]
fn test_insphere_from_matrix_stage2_exact_via_overflow() {
let k = 4;
let big = 1e100;
with_la_stack_matrix!(k, |m| {
matrix_set(&mut m, 0, 0, big);
matrix_set(&mut m, 1, 1, big);
matrix_set(&mut m, 2, 2, big);
matrix_set(&mut m, 3, 3, big);
assert_eq!(insphere_from_matrix(&m, k, 1), InSphere::INSIDE);
assert_eq!(insphere_from_matrix(&m, k, -1), InSphere::OUTSIDE);
});
}
#[test]
fn test_insphere_from_matrix_stage2_near_singular() {
let k = 3;
let eps = f64::EPSILON;
with_la_stack_matrix!(k, |m| {
matrix_set(&mut m, 0, 0, 1.0);
matrix_set(&mut m, 0, 1, 1.0);
matrix_set(&mut m, 1, 0, 1.0);
matrix_set(&mut m, 1, 1, 1.0 + eps);
matrix_set(&mut m, 2, 2, 1.0);
assert_eq!(insphere_from_matrix(&m, k, 1), InSphere::INSIDE);
});
}
#[test]
fn test_insphere_from_matrix_stage2_boundary() {
let k = 3;
with_la_stack_matrix!(k, |m| {
matrix_set(&mut m, 0, 0, 1.0);
matrix_set(&mut m, 0, 1, 2.0);
matrix_set(&mut m, 0, 2, 3.0);
matrix_set(&mut m, 1, 0, 1.0);
matrix_set(&mut m, 1, 1, 2.0);
matrix_set(&mut m, 1, 2, 3.0);
matrix_set(&mut m, 2, 0, 4.0);
matrix_set(&mut m, 2, 1, 5.0);
matrix_set(&mut m, 2, 2, 6.0);
assert_eq!(insphere_from_matrix(&m, k, 1), InSphere::BOUNDARY);
});
}
#[test]
fn test_insphere_from_matrix_stage3_nan() {
let k = 3;
with_la_stack_matrix!(k, |m| {
matrix_set(&mut m, 0, 0, 1.0);
matrix_set(&mut m, 1, 1, 1.0);
matrix_set(&mut m, 2, 2, f64::NAN);
assert_eq!(insphere_from_matrix(&m, k, 1), InSphere::BOUNDARY);
});
}
#[test]
fn test_insphere_wrong_point_count() {
let two_points: Vec<Point<f64, 3>> =
vec![Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])];
let result = insphere(&two_points, Point::new([0.5, 0.5, 0.5]));
assert!(result.is_err(), "insphere with 2 points in 3D should error");
}
#[test]
fn test_insphere_lifted_overflow_test_point_squared_norm() {
let simplex = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([0.0, 0.0, 1.0]),
];
let far_point = Point::new([1e155, 0.0, 0.0]);
let result = insphere_lifted(&simplex, far_point);
assert!(
result.is_err(),
"insphere_lifted should error when test point squared norm overflows"
);
}
#[test]
fn test_exact_orientation_truly_degenerate() {
let collinear_2d = vec![
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([2.0, 0.0]),
];
assert_eq!(
simplex_orientation(&collinear_2d).unwrap(),
Orientation::DEGENERATE,
"Exactly collinear points must be DEGENERATE"
);
let coplanar_3d = vec![
Point::new([1.0, 2.0, 3.0]),
Point::new([4.0, 5.0, 6.0]),
Point::new([5.0, 7.0, 9.0]),
Point::new([0.0, 0.0, 0.0]),
];
assert_eq!(
simplex_orientation(&coplanar_3d).unwrap(),
Orientation::DEGENERATE,
"Linearly dependent 3D simplex must be DEGENERATE"
);
}
}