#![forbid(unsafe_code)]
use crate::core::simplex::SimplexValidationError;
use crate::geometry::matrix::{
Matrix, StackMatrixDispatchError, matrix_fast_filter, matrix_get, matrix_set, matrix_zero_like,
};
use crate::geometry::point::Point;
use crate::geometry::sos::exact_det_sign;
use crate::geometry::traits::coordinate::{
CoordinateConversionError, DEFAULT_TOLERANCE_F64, DegenerateSimplexReason,
InvalidCoordinateValue,
};
use crate::geometry::util::{circumcenter, circumradius_with_center, hypot, squared_norm};
use crate::prelude::CircumcenterError;
use core::hint::cold_path;
#[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
}
}
fn validate_active_matrix_dimension<const N: usize>(
k: usize,
) -> Result<(), StackMatrixDispatchError> {
if k == N {
return Ok(());
}
Err(StackMatrixDispatchError::ActiveBlockDimensionMismatch { k, dim: N })
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct RelativeInsphereSigns {
pub(crate) relative_orientation: i32,
pub(crate) insphere_determinant: i32,
}
#[inline]
pub(crate) const fn relative_insphere_effective_sign(signs: RelativeInsphereSigns) -> i32 {
let effective = signs.insphere_determinant * -signs.relative_orientation;
if effective > 0 {
1
} else if effective < 0 {
-1
} else {
0
}
}
#[inline]
pub(crate) const fn relative_insphere_classification(signs: RelativeInsphereSigns) -> InSphere {
let sign = relative_insphere_effective_sign(signs);
if sign > 0 {
InSphere::INSIDE
} else if sign < 0 {
InSphere::OUTSIDE
} else {
InSphere::BOUNDARY
}
}
#[inline]
fn fill_relative_insphere_matrix<const D: usize, const K: usize>(
matrix: &mut Matrix<K>,
simplex_points: &[Point<D>],
test_point: &Point<D>,
) -> Result<(), CoordinateConversionError> {
if K != D + 1 {
return Err(
StackMatrixDispatchError::ActiveBlockDimensionMismatch { k: D + 1, dim: K }.into(),
);
}
if simplex_points.len() != D + 1 {
return Err(CoordinateConversionError::InvalidSimplexPointCount {
actual: simplex_points.len(),
expected: D + 1,
dimension: D,
});
}
let reference_coords = simplex_points[0].coords();
for (row, point) in simplex_points.iter().skip(1).enumerate() {
let mut relative_coords = [0.0; D];
for (dst, (point_coord, reference_coord)) in relative_coords
.iter_mut()
.zip(point.coords().iter().zip(reference_coords.iter()))
{
*dst = *point_coord - *reference_coord;
}
for (column, &value) in relative_coords.iter().enumerate() {
matrix_set(matrix, row, column, value)?;
}
let lifted = squared_norm(&relative_coords);
if !lifted.is_finite() {
return Err(CoordinateConversionError::NonFiniteValue {
coordinate_index: D,
coordinate_value: InvalidCoordinateValue::from_debug(&lifted),
});
}
matrix_set(matrix, row, D, lifted)?;
}
let mut test_relative_coords = [0.0; D];
for (dst, (point_coord, reference_coord)) in test_relative_coords
.iter_mut()
.zip(test_point.coords().iter().zip(reference_coords.iter()))
{
*dst = *point_coord - *reference_coord;
}
for (column, &value) in test_relative_coords.iter().enumerate() {
matrix_set(matrix, D, column, value)?;
}
let lifted = squared_norm(&test_relative_coords);
if !lifted.is_finite() {
return Err(CoordinateConversionError::NonFiniteValue {
coordinate_index: D,
coordinate_value: InvalidCoordinateValue::from_debug(&lifted),
});
}
matrix_set(matrix, D, D, lifted)?;
Ok(())
}
#[inline]
pub(crate) fn relative_insphere_signs<const D: usize>(
simplex_points: &[Point<D>],
test_point: &Point<D>,
) -> Result<RelativeInsphereSigns, CoordinateConversionError> {
if simplex_points.len() != D + 1 {
return Err(CoordinateConversionError::InvalidSimplexPointCount {
actual: simplex_points.len(),
expected: D + 1,
dimension: D,
});
}
let k = D + 1;
try_with_la_stack_matrix!(k, |matrix| {
fill_relative_insphere_matrix(&mut matrix, simplex_points, test_point)?;
let mut orientation_matrix = matrix_zero_like(&matrix);
for i in 0..D {
for j in 0..D {
matrix_set(&mut orientation_matrix, i, j, matrix_get(&matrix, i, j)?)?;
}
}
matrix_set(&mut orientation_matrix, D, D, 1.0)?;
Ok(RelativeInsphereSigns {
relative_orientation: exact_det_sign(&orientation_matrix),
insphere_determinant: exact_det_sign(&matrix),
})
})
}
#[inline]
pub(crate) fn relative_insphere_determinant_sign<const D: usize>(
simplex_points: &[Point<D>],
test_point: &Point<D>,
) -> Result<i32, CoordinateConversionError> {
if simplex_points.len() != D + 1 {
return Err(CoordinateConversionError::InvalidSimplexPointCount {
actual: simplex_points.len(),
expected: D + 1,
dimension: D,
});
}
let k = D + 1;
try_with_la_stack_matrix!(k, |matrix| {
fill_relative_insphere_matrix(&mut matrix, simplex_points, test_point)?;
if let Some((det, errbound)) = matrix_fast_filter(&matrix)? {
if det > errbound {
return Ok(1);
}
if det < -errbound {
return Ok(-1);
}
}
cold_path();
Ok(exact_det_sign(&matrix))
})
}
#[inline]
pub(crate) fn try_insphere_from_matrix<const N: usize>(
matrix: &Matrix<N>,
k: usize,
orient_sign: i8,
) -> Result<InSphere, StackMatrixDispatchError> {
validate_active_matrix_dimension::<N>(k)?;
if let Some((det, errbound)) = matrix_fast_filter(matrix)? {
let det_norm = det * f64::from(orient_sign);
if det_norm > errbound {
return Ok(InSphere::INSIDE);
}
if det_norm < -errbound {
return Ok(InSphere::OUTSIDE);
}
}
cold_path();
Ok(sign_to_insphere(
matrix.det_sign_exact().as_i8(),
orient_sign,
))
}
#[inline]
pub(crate) fn try_orientation_from_matrix<const N: usize>(
matrix: &Matrix<N>,
k: usize,
) -> Result<Orientation, StackMatrixDispatchError> {
validate_active_matrix_dimension::<N>(k)?;
if let Some((det, errbound)) = matrix_fast_filter(matrix)? {
if det > errbound {
return Ok(Orientation::POSITIVE);
}
if det < -errbound {
return Ok(Orientation::NEGATIVE);
}
}
cold_path();
Ok(sign_to_orientation(matrix.det_sign_exact().as_i8()))
}
#[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<const D: usize>(
simplex_points: &[Point<D>],
) -> Result<Orientation, CoordinateConversionError> {
if simplex_points.len() != D + 1 {
return Err(CoordinateConversionError::InvalidSimplexPointCount {
actual: simplex_points.len(),
expected: D + 1,
dimension: D,
});
}
let k = D + 1;
try_with_la_stack_matrix!(k, |matrix| {
for (i, p) in simplex_points.iter().enumerate() {
for (j, &v) in p.coords().iter().enumerate() {
matrix_set(&mut matrix, i, j, v)?;
}
matrix_set(&mut matrix, i, D, 1.0)?;
}
Ok(try_orientation_from_matrix(&matrix, k)?)
})
}
#[inline]
pub(crate) fn simplex_orientation_fast_filter_sign<const D: usize>(
simplex_points: &[Point<D>],
) -> Result<Option<i32>, CoordinateConversionError> {
if simplex_points.len() != D + 1 {
return Err(CoordinateConversionError::InvalidSimplexPointCount {
actual: simplex_points.len(),
expected: D + 1,
dimension: D,
});
}
let k = D + 1;
try_with_la_stack_matrix!(k, |matrix| {
for (i, p) in simplex_points.iter().enumerate() {
for (j, &v) in p.coords().iter().enumerate() {
matrix_set(&mut matrix, i, j, v)?;
}
matrix_set(&mut matrix, i, D, 1.0)?;
}
if let Some((det, errbound)) = matrix_fast_filter(&matrix)? {
if det > errbound {
return Ok(Some(1));
}
if det < -errbound {
return Ok(Some(-1));
}
}
Ok(None)
})
}
pub fn insphere_distance<const D: usize>(
simplex_points: &[Point<D>],
test_point: Point<D>,
) -> Result<InSphere, CircumcenterError> {
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 = [0.0; 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 = DEFAULT_TOLERANCE_F64;
let scale = 1.0_f64.max(circumradius.abs().max(radius.abs()));
let tolerance = base_tolerance * scale;
let signed_margin = circumradius - radius;
if signed_margin.abs() <= tolerance {
Ok(InSphere::BOUNDARY)
} else if signed_margin > 0.0 {
Ok(InSphere::INSIDE)
} else {
Ok(InSphere::OUTSIDE)
}
}
#[inline]
pub fn insphere<const D: usize>(
simplex_points: &[Point<D>],
test_point: Point<D>,
) -> Result<InSphere, CoordinateConversionError> {
if simplex_points.len() != D + 1 {
return Err(CoordinateConversionError::InvalidSimplexPointCount {
actual: simplex_points.len(),
expected: D + 1,
dimension: D,
});
}
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();
for (j, &v) in point_coords.iter().enumerate() {
matrix_set(&mut matrix, i, j, v)?;
}
matrix_set(&mut matrix, i, D, squared_norm(point_coords))?;
matrix_set(&mut matrix, i, D + 1, 1.0)?;
}
let test_point_coords = test_point.coords();
for (j, &v) in test_point_coords.iter().enumerate() {
matrix_set(&mut matrix, D + 1, j, v)?;
}
matrix_set(&mut matrix, D + 1, D, squared_norm(test_point_coords))?;
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 = try_orientation_from_matrix(&orient_matrix, k)?;
match orientation {
Orientation::DEGENERATE => Err(CoordinateConversionError::DegenerateSimplex {
dimension: D,
reason: DegenerateSimplexReason::ZeroOrientation,
}),
Orientation::POSITIVE => Ok(try_insphere_from_matrix(&matrix, k, 1)?),
Orientation::NEGATIVE => Ok(try_insphere_from_matrix(&matrix, k, -1)?),
}
})
}
pub fn insphere_lifted<const D: usize>(
simplex_points: &[Point<D>],
test_point: Point<D>,
) -> Result<InSphere, SimplexValidationError> {
if simplex_points.len() != D + 1 {
return Err(SimplexValidationError::InsufficientVertices {
actual: simplex_points.len(),
expected: D + 1,
dimension: D,
});
}
let signs = relative_insphere_signs(simplex_points, &test_point)
.map_err(|source| SimplexValidationError::CoordinateConversion { source })?;
if signs.relative_orientation == 0 {
Err(SimplexValidationError::DegenerateSimplex)
} else {
Ok(relative_insphere_classification(signs))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::matrix::{LaError, matrix_set as try_matrix_set};
use crate::prelude::circumradius;
use approx::assert_relative_eq;
use std::assert_matches;
use std::collections::HashMap;
fn set_test_matrix_entry<const N: usize>(
matrix: &mut Matrix<N>,
row: usize,
column: usize,
value: f64,
) {
try_matrix_set(matrix, row, column, value).unwrap();
}
#[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::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.0, 1.0]).expect("finite point coordinates"),
];
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::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 radius_3d = circumradius(&tetrahedron_3d).unwrap();
tracing::debug!("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::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 radius_4d = circumradius(&simplex_4d).unwrap();
tracing::debug!("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::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"),
Point::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).expect("finite point coordinates"),
];
let radius_5d = circumradius(&simplex_5d).unwrap();
tracing::debug!("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"
);
tracing::debug!("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;
tracing::debug!(" 2D (right triangle): {radius_2d} ≈ {expected_2d:.6}");
tracing::debug!(" 3D (unit tetrahedron): {radius_3d} ≈ {expected_3d:.6}");
tracing::debug!(" 4D (unit 4-simplex): {radius_4d} = 1.0");
tracing::debug!(" 5D (unit 5-simplex): {radius_5d} ≈ {expected_5d:.6}");
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "dimension sweep keeps comparable insphere cases together"
)]
fn test_insphere_basic_functionality_2d_to_5d() {
let simplex_2d = 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.0, 1.0]).expect("finite point coordinates"),
];
assert_eq!(
insphere_lifted(
&simplex_2d,
Point::try_new([10.0, 10.0]).expect("finite point coordinates")
)
.unwrap(),
InSphere::OUTSIDE,
"2D far outside point should be OUTSIDE"
);
assert_eq!(
insphere_lifted(
&simplex_2d,
Point::try_new([0.1, 0.1]).expect("finite point coordinates")
)
.unwrap(),
InSphere::INSIDE,
"2D inside point should be INSIDE"
);
assert_eq!(
insphere_lifted(
&simplex_2d,
Point::try_new([0.0, 0.0]).expect("finite point coordinates")
)
.unwrap(),
InSphere::BOUNDARY,
"2D vertex should be BOUNDARY"
);
let simplex_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"),
Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"),
];
assert_eq!(
insphere_lifted(
&simplex_3d,
Point::try_new([10.0, 10.0, 10.0]).expect("finite point coordinates")
)
.unwrap(),
InSphere::OUTSIDE,
"3D far outside point should be OUTSIDE"
);
assert_eq!(
insphere_lifted(
&simplex_3d,
Point::try_new([0.1, 0.1, 0.1]).expect("finite point coordinates")
)
.unwrap(),
InSphere::INSIDE,
"3D inside point should be INSIDE"
);
assert_eq!(
insphere_lifted(
&simplex_3d,
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates")
)
.unwrap(),
InSphere::BOUNDARY,
"3D vertex should be BOUNDARY"
);
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"),
];
assert_eq!(
insphere_lifted(
&simplex_4d,
Point::try_new([10.0, 10.0, 10.0, 10.0]).expect("finite point coordinates")
)
.unwrap(),
InSphere::OUTSIDE,
"4D far outside point should be OUTSIDE"
);
assert_eq!(
insphere_lifted(
&simplex_4d,
Point::try_new([0.1, 0.1, 0.1, 0.1]).expect("finite point coordinates")
)
.unwrap(),
InSphere::INSIDE,
"4D inside point should be INSIDE"
);
assert_eq!(
insphere_lifted(
&simplex_4d,
Point::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates")
)
.unwrap(),
InSphere::BOUNDARY,
"4D vertex should be BOUNDARY"
);
let simplex_5d = 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"),
Point::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).expect("finite point coordinates"),
];
assert_eq!(
insphere_lifted(
&simplex_5d,
Point::try_new([10.0, 10.0, 10.0, 10.0, 10.0]).expect("finite point coordinates")
)
.unwrap(),
InSphere::OUTSIDE,
"5D far outside point should be OUTSIDE"
);
assert_eq!(
insphere_lifted(
&simplex_5d,
Point::try_new([0.1, 0.1, 0.1, 0.1, 0.1]).expect("finite point coordinates")
)
.unwrap(),
InSphere::INSIDE,
"5D inside point should be INSIDE"
);
assert_eq!(
insphere_lifted(
&simplex_5d,
Point::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates")
)
.unwrap(),
InSphere::BOUNDARY,
"5D vertex should be BOUNDARY"
);
}
#[test]
fn test_insphere_edge_cases_and_errors() {
let simplex_1d = vec![
Point::try_new([0.0]).expect("finite point coordinates"),
Point::try_new([2.0]).expect("finite point coordinates"),
];
let midpoint_1d = Point::try_new([1.0]).expect("finite point coordinates");
let far_point_1d = Point::try_new([10.0]).expect("finite point coordinates");
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::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 circumcenter_3d = Point::try_new([0.5, 0.5, 0.5]).expect("finite point coordinates");
assert_eq!(
insphere_lifted(&simplex_3d, circumcenter_3d).unwrap(),
InSphere::INSIDE,
"3D circumcenter should be INSIDE"
);
let regular_4d_simplex = vec![
Point::try_new([1.0, 1.0, 1.0, 1.0]).expect("finite point coordinates"),
Point::try_new([1.0, -1.0, -1.0, -1.0]).expect("finite point coordinates"),
Point::try_new([-1.0, 1.0, -1.0, -1.0]).expect("finite point coordinates"),
Point::try_new([-1.0, -1.0, 1.0, -1.0]).expect("finite point coordinates"),
Point::try_new([-1.0, -1.0, -1.0, 1.0]).expect("finite point coordinates"),
];
assert_eq!(
insphere_lifted(
®ular_4d_simplex,
Point::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates")
)
.unwrap(),
InSphere::INSIDE,
"Origin should be inside symmetric 4D simplex"
);
let incomplete_simplex = 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 test_point = Point::try_new([0.5, 0.5, 0.5]).expect("finite point coordinates");
assert!(
insphere_lifted(&incomplete_simplex, test_point).is_err(),
"Should error with insufficient vertices"
);
}
#[test]
fn test_relative_insphere_determinant_sign_rejects_wrong_point_count() {
let incomplete_simplex = 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 test_point = Point::try_new([0.5, 0.5, 0.5]).expect("finite point coordinates");
let err = relative_insphere_determinant_sign(&incomplete_simplex, &test_point).unwrap_err();
assert_eq!(
err,
CoordinateConversionError::InvalidSimplexPointCount {
actual: 2,
expected: 4,
dimension: 3,
}
);
}
#[test]
fn test_relative_insphere_determinant_sign_boundary_matches_exact_signs() {
let simplex = 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.0, 1.0]).expect("finite point coordinates"),
];
let boundary = Point::try_new([1.0, 1.0]).expect("finite point coordinates");
let determinant_sign = relative_insphere_determinant_sign(&simplex, &boundary).unwrap();
let signs = relative_insphere_signs(&simplex, &boundary).unwrap();
assert_eq!(determinant_sign, 0);
assert_eq!(determinant_sign, signs.insphere_determinant);
}
#[test]
fn predicates_circumcenter_error_cases() {
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 center_result = circumcenter(&points);
assert!(center_result.is_err());
}
#[test]
fn predicates_circumcenter_collinear_points() {
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"),
Point::try_new([3.0, 0.0, 0.0]).expect("finite point coordinates"),
];
let center_result = circumcenter(&points);
assert!(center_result.is_err());
}
#[test]
fn predicates_circumsphere_edge_cases() {
let simplex_points = 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.0, 1.0]).expect("finite point coordinates"),
];
let test_point = Point::try_new([0.25, 0.25]).expect("finite point coordinates");
assert!(insphere_distance(&simplex_points, test_point).is_ok());
let far_point = Point::try_new([100.0, 100.0]).expect("finite point coordinates");
assert!(insphere_distance(&simplex_points, far_point).is_ok());
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "orientation regression test keeps dimension-specific cases together"
)]
fn test_simplex_orientation_comprehensive() {
let positive_2d = 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.0, 1.0]).expect("finite point coordinates"),
];
assert_eq!(
simplex_orientation(&positive_2d).unwrap(),
Orientation::POSITIVE,
"2D positive orientation failed"
);
let negative_2d = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0]).expect("finite point coordinates"),
];
assert_eq!(
simplex_orientation(&negative_2d).unwrap(),
Orientation::NEGATIVE,
"2D negative orientation failed"
);
let degenerate_2d = 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"),
];
assert_eq!(
simplex_orientation(°enerate_2d).unwrap(),
Orientation::DEGENERATE,
"2D degenerate case failed"
);
let positive_3d = 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([1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"),
];
assert_eq!(
simplex_orientation(&positive_3d).unwrap(),
Orientation::POSITIVE,
"3D positive orientation failed"
);
let negative_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"),
Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"),
];
assert_eq!(
simplex_orientation(&negative_3d).unwrap(),
Orientation::NEGATIVE,
"3D negative orientation failed"
);
let degenerate_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"),
Point::try_new([1.0, 1.0, 0.0]).expect("finite point coordinates"), ];
assert_eq!(
simplex_orientation(°enerate_3d).unwrap(),
Orientation::DEGENERATE,
"3D degenerate case failed"
);
let positive_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"),
];
assert_eq!(
simplex_orientation(&positive_4d).unwrap(),
Orientation::POSITIVE,
"4D positive orientation failed"
);
let negative_4d = vec![
Point::try_new([0.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([1.0, 0.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"),
];
assert_eq!(
simplex_orientation(&negative_4d).unwrap(),
Orientation::NEGATIVE,
"4D negative orientation failed"
);
let degenerate_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([1.0, 1.0, 1.0, 0.0]).expect("finite point coordinates"), ];
assert_eq!(
simplex_orientation(°enerate_4d).unwrap(),
Orientation::DEGENERATE,
"4D degenerate case failed"
);
let positive_5d = vec![
Point::try_new([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]).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, 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"),
Point::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).expect("finite point coordinates"),
];
assert_eq!(
simplex_orientation(&positive_5d).unwrap(),
Orientation::POSITIVE,
"5D positive orientation failed"
);
let negative_5d = 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"),
Point::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).expect("finite point coordinates"),
];
assert_eq!(
simplex_orientation(&negative_5d).unwrap(),
Orientation::NEGATIVE,
"5D negative orientation failed"
);
let degenerate_5d = 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"),
Point::try_new([1.0, 1.0, 1.0, 1.0, 0.0]).expect("finite point coordinates"), ];
assert_eq!(
simplex_orientation(°enerate_5d).unwrap(),
Orientation::DEGENERATE,
"5D degenerate case failed"
);
let insufficient_vertices = 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"),
]; 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::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([1.0, 1.0, 0.0]).expect("finite point coordinates"), ];
let test_point = Point::try_new([0.5, 0.5, 0.5]).expect("finite point coordinates");
let result = insphere(°enerate_simplex, test_point);
assert_eq!(
result,
Err(CoordinateConversionError::DegenerateSimplex {
dimension: 3,
reason: DegenerateSimplexReason::ZeroOrientation,
})
);
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(SimplexValidationError::DegenerateSimplex) => (), Err(other) => panic!("Wrong error type: {other:?}"),
Ok(_) => panic!("Function should have returned an error"),
}
let insufficient_vertices = 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"),
];
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::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.0, 1.0]).expect("finite point coordinates"),
];
let vertex_point = Point::try_new([0.0, 0.0]).expect("finite point coordinates");
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::try_new([0.1, 0.1]).expect("finite point coordinates");
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::try_new([10.0, 10.0]).expect("finite point coordinates");
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::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 test_cases = [
(
Point::try_new([0.2, 0.2, 0.2]).expect("finite point coordinates"),
InSphere::INSIDE,
),
(
Point::try_new([2.0, 2.0, 2.0]).expect("finite point coordinates"),
InSphere::OUTSIDE,
),
(
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
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::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.0, 1.0]).expect("finite point coordinates"),
];
let test_cases = [
(
Point::try_new([0.1, 0.1]).expect("finite point coordinates"),
"inside",
), (
Point::try_new([10.0, 10.0]).expect("finite point coordinates"),
"outside",
), (
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
"boundary",
), (
Point::try_new([0.5, 0.0]).expect("finite point coordinates"),
"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();
tracing::debug!(
"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::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 test_cases = [
(
Point::try_new([0.1, 0.1, 0.1]).expect("finite point coordinates"),
"inside",
),
(
Point::try_new([10.0, 10.0, 10.0]).expect("finite point coordinates"),
"outside",
),
(
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
"boundary",
), (
Point::try_new([0.25, 0.25, 0.25]).expect("finite point coordinates"),
"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();
tracing::debug!(
"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::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 test_cases = [
(
Point::try_new([0.1, 0.1, 0.1, 0.1]).expect("finite point coordinates"),
"inside",
),
(
Point::try_new([10.0, 10.0, 10.0, 10.0]).expect("finite point coordinates"),
"outside",
),
(
Point::try_new([0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
"boundary",
), (
Point::try_new([0.2, 0.2, 0.2, 0.2]).expect("finite point coordinates"),
"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();
tracing::debug!(
"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::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"),
Point::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).expect("finite point coordinates"),
];
let test_cases = [
(
Point::try_new([0.1, 0.1, 0.1, 0.1, 0.1]).expect("finite point coordinates"),
"inside",
),
(
Point::try_new([10.0, 10.0, 10.0, 10.0, 10.0]).expect("finite point coordinates"),
"outside",
),
(
Point::try_new([0.0, 0.0, 0.0, 0.0, 0.0]).expect("finite point coordinates"),
"boundary",
), (
Point::try_new([0.15, 0.15, 0.15, 0.15, 0.15]).expect("finite point coordinates"),
"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();
tracing::debug!(
"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::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1e-6, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1e-6]).expect("finite point coordinates"),
];
let test_point_2d = Point::try_new([1e-7, 1e-7]).expect("finite point coordinates");
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::try_new([1e6, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1e6 + 1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1e6, 1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1e6, 0.0, 1.0]).expect("finite point coordinates"),
];
let test_point_3d =
Point::try_new([1e6 + 0.1, 0.1, 0.1]).expect("finite point coordinates");
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::try_new([-1.0, -1.0, -1.0, -1.0]).expect("finite point coordinates"),
Point::try_new([0.0, -1.0, -1.0, -1.0]).expect("finite point coordinates"),
Point::try_new([-1.0, 0.0, -1.0, -1.0]).expect("finite point coordinates"),
Point::try_new([-1.0, -1.0, 0.0, -1.0]).expect("finite point coordinates"),
Point::try_new([-1.0, -1.0, -1.0, 0.0]).expect("finite point coordinates"),
];
let test_point_4d =
Point::try_new([-0.5, -0.5, -0.5, -0.5]).expect("finite point coordinates");
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::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 test_points = [
Point::try_new([0.1, 0.1, 0.1]).expect("finite point coordinates"),
Point::try_new([0.3, 0.2, 0.1]).expect("finite point coordinates"),
Point::try_new([0.5, 0.5, 0.5]).expect("finite point coordinates"),
Point::try_new([1.0, 1.0, 1.0]).expect("finite point coordinates"),
Point::try_new([2.0, 2.0, 2.0]).expect("finite point coordinates"),
Point::try_new([-0.1, -0.1, -0.1]).expect("finite point coordinates"),
Point::try_new([0.25, 0.25, 0.25]).expect("finite point coordinates"),
Point::try_new([0.01, 0.01, 0.01]).expect("finite point coordinates"),
];
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;
}
}
tracing::debug!("Stress test results: {total_tests} total tests");
for (key, count) in &disagreement_count {
tracing::debug!(" {key}: {count} disagreements");
}
assert_eq!(
disagreement_count.len(),
0,
"All methods should agree after sign fix"
);
}
fn check_insphere_parity<const D: usize>(
simplex: &[Point<D>],
test_point: Point<D>,
expected_orientation: Orientation,
expected_result: InSphere,
dimension: usize,
orientation_label: &str,
) {
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::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.0, 1.0]).expect("finite point coordinates"),
],
Point::try_new([0.1, 0.1]).expect("finite point coordinates"),
Orientation::POSITIVE,
InSphere::INSIDE,
2,
"POSITIVE",
);
check_insphere_parity(
&[
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([1.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"),
],
Point::try_new([0.1, 0.1, 0.1]).expect("finite point coordinates"),
Orientation::POSITIVE,
InSphere::INSIDE,
3,
"POSITIVE",
);
check_insphere_parity(
&[
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"),
],
Point::try_new([0.1, 0.1, 0.1, 0.1]).expect("finite point coordinates"),
Orientation::POSITIVE,
InSphere::INSIDE,
4,
"POSITIVE",
);
check_insphere_parity(
&[
Point::try_new([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]).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, 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"),
Point::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).expect("finite point coordinates"),
],
Point::try_new([0.1, 0.1, 0.1, 0.1, 0.1]).expect("finite point coordinates"),
Orientation::POSITIVE,
InSphere::INSIDE,
5,
"POSITIVE",
);
}
#[test]
fn test_insphere_lifted_parity_branch_negative_orientation() {
check_insphere_parity(
&[
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 1.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0]).expect("finite point coordinates"),
],
Point::try_new([0.1, 0.1]).expect("finite point coordinates"),
Orientation::NEGATIVE,
InSphere::INSIDE,
2,
"NEGATIVE",
);
check_insphere_parity(
&[
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"),
],
Point::try_new([0.1, 0.1, 0.1]).expect("finite point coordinates"),
Orientation::NEGATIVE,
InSphere::INSIDE,
3,
"NEGATIVE",
);
check_insphere_parity(
&[
Point::try_new([0.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([1.0, 0.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"),
],
Point::try_new([0.1, 0.1, 0.1, 0.1]).expect("finite point coordinates"),
Orientation::NEGATIVE,
InSphere::INSIDE,
4,
"NEGATIVE",
);
check_insphere_parity(
&[
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"),
Point::try_new([0.0, 0.0, 0.0, 0.0, 1.0]).expect("finite point coordinates"),
],
Point::try_new([0.1, 0.1, 0.1, 0.1, 0.1]).expect("finite point coordinates"),
Orientation::NEGATIVE,
InSphere::INSIDE,
5,
"NEGATIVE",
);
}
#[test]
fn test_orientation_from_matrix_positive() {
let k = 3;
with_la_stack_matrix!(k, |m| {
set_test_matrix_entry(&mut m, 0, 0, 0.0);
set_test_matrix_entry(&mut m, 0, 1, 0.0);
set_test_matrix_entry(&mut m, 0, 2, 1.0);
set_test_matrix_entry(&mut m, 1, 0, 1.0);
set_test_matrix_entry(&mut m, 1, 1, 0.0);
set_test_matrix_entry(&mut m, 1, 2, 1.0);
set_test_matrix_entry(&mut m, 2, 0, 0.0);
set_test_matrix_entry(&mut m, 2, 1, 1.0);
set_test_matrix_entry(&mut m, 2, 2, 1.0);
assert_eq!(
try_orientation_from_matrix(&m, k).unwrap(),
Orientation::POSITIVE
);
});
}
#[test]
fn test_orientation_from_matrix_negative() {
let k = 3;
with_la_stack_matrix!(k, |m| {
set_test_matrix_entry(&mut m, 0, 0, 0.0);
set_test_matrix_entry(&mut m, 0, 1, 1.0);
set_test_matrix_entry(&mut m, 0, 2, 1.0);
set_test_matrix_entry(&mut m, 1, 0, 1.0);
set_test_matrix_entry(&mut m, 1, 1, 0.0);
set_test_matrix_entry(&mut m, 1, 2, 1.0);
set_test_matrix_entry(&mut m, 2, 0, 0.0);
set_test_matrix_entry(&mut m, 2, 1, 0.0);
set_test_matrix_entry(&mut m, 2, 2, 1.0);
assert_eq!(
try_orientation_from_matrix(&m, k).unwrap(),
Orientation::NEGATIVE
);
});
}
#[test]
fn test_orientation_from_matrix_degenerate() {
let k = 3;
with_la_stack_matrix!(k, |m| {
set_test_matrix_entry(&mut m, 0, 0, 0.0);
set_test_matrix_entry(&mut m, 0, 1, 0.0);
set_test_matrix_entry(&mut m, 0, 2, 1.0);
set_test_matrix_entry(&mut m, 1, 0, 1.0);
set_test_matrix_entry(&mut m, 1, 1, 0.0);
set_test_matrix_entry(&mut m, 1, 2, 1.0);
set_test_matrix_entry(&mut m, 2, 0, 2.0);
set_test_matrix_entry(&mut m, 2, 1, 0.0);
set_test_matrix_entry(&mut m, 2, 2, 1.0);
assert_eq!(
try_orientation_from_matrix(&m, k).unwrap(),
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| {
set_test_matrix_entry(&mut m, 0, 0, 0.0);
set_test_matrix_entry(&mut m, 0, 1, 0.0);
set_test_matrix_entry(&mut m, 0, 2, 1.0);
set_test_matrix_entry(&mut m, 1, 0, big);
set_test_matrix_entry(&mut m, 1, 1, 0.0);
set_test_matrix_entry(&mut m, 1, 2, 1.0);
set_test_matrix_entry(&mut m, 2, 0, 0.0);
set_test_matrix_entry(&mut m, 2, 1, big);
set_test_matrix_entry(&mut m, 2, 2, 1.0);
let result = try_orientation_from_matrix(&m, k).unwrap();
assert_eq!(
result,
Orientation::POSITIVE,
"Extreme-magnitude fallback should still resolve correct orientation"
);
});
}
#[test]
fn test_orientation_matrix_rejects_nonfinite_entry_at_set_boundary() {
let k = 4;
with_la_stack_matrix!(k, |m| {
let err = try_matrix_set(&mut m, 3, 3, f64::NAN).unwrap_err();
assert_eq!(
err,
StackMatrixDispatchError::La {
source: LaError::non_finite_input_matrix(3, 3),
}
);
});
}
#[test]
fn test_try_orientation_from_matrix_rejects_dimension_mismatch() {
let matrix = Matrix::<3>::zero();
let err = try_orientation_from_matrix(&matrix, 4).unwrap_err();
assert_matches!(
err,
StackMatrixDispatchError::ActiveBlockDimensionMismatch { k: 4, dim: 3 }
);
}
#[test]
fn test_fill_relative_insphere_matrix_rejects_dimension_mismatch() {
let simplex_points = [
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.0, 1.0]).expect("finite point coordinates"),
];
let test_point = Point::try_new([0.25, 0.25]).expect("finite point coordinates");
let mut matrix = Matrix::<4>::zero();
let err = fill_relative_insphere_matrix::<2, 4>(&mut matrix, &simplex_points, &test_point)
.unwrap_err();
assert_matches!(
err,
CoordinateConversionError::MatrixDimensionMismatch {
active: 3,
matrix_dimension: 4
}
);
}
#[test]
fn test_fill_relative_insphere_matrix_rejects_simplex_point_count_mismatch() {
let simplex_points = [
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0]).expect("finite point coordinates"),
];
let test_point = Point::try_new([0.25, 0.25]).expect("finite point coordinates");
let mut matrix = Matrix::<3>::zero();
let err = fill_relative_insphere_matrix::<2, 3>(&mut matrix, &simplex_points, &test_point)
.unwrap_err();
assert_matches!(
err,
CoordinateConversionError::InvalidSimplexPointCount {
actual: 2,
expected: 3,
dimension: 2
}
);
}
#[test]
fn test_exact_orientation_near_degenerate_2d() {
let eps = f64::from_bits(0x3CD0_0000_0000_0000); let nearly_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([0.5, eps]).expect("finite point coordinates"),
];
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::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, eps]).expect("finite point coordinates"),
];
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| {
set_test_matrix_entry(&mut m, 0, 0, big);
set_test_matrix_entry(&mut m, 1, 1, big);
set_test_matrix_entry(&mut m, 2, 2, big);
set_test_matrix_entry(&mut m, 3, 3, big);
assert_eq!(
try_insphere_from_matrix(&m, k, 1).unwrap(),
InSphere::INSIDE
);
assert_eq!(
try_insphere_from_matrix(&m, k, -1).unwrap(),
InSphere::OUTSIDE
);
});
}
#[test]
fn test_insphere_from_matrix_stage2_near_singular() {
let k = 3;
let eps = f64::EPSILON;
with_la_stack_matrix!(k, |m| {
set_test_matrix_entry(&mut m, 0, 0, 1.0);
set_test_matrix_entry(&mut m, 0, 1, 1.0);
set_test_matrix_entry(&mut m, 1, 0, 1.0);
set_test_matrix_entry(&mut m, 1, 1, 1.0 + eps);
set_test_matrix_entry(&mut m, 2, 2, 1.0);
assert_eq!(
try_insphere_from_matrix(&m, k, 1).unwrap(),
InSphere::INSIDE
);
});
}
#[test]
fn test_insphere_from_matrix_stage2_boundary() {
let k = 3;
with_la_stack_matrix!(k, |m| {
set_test_matrix_entry(&mut m, 0, 0, 1.0);
set_test_matrix_entry(&mut m, 0, 1, 2.0);
set_test_matrix_entry(&mut m, 0, 2, 3.0);
set_test_matrix_entry(&mut m, 1, 0, 1.0);
set_test_matrix_entry(&mut m, 1, 1, 2.0);
set_test_matrix_entry(&mut m, 1, 2, 3.0);
set_test_matrix_entry(&mut m, 2, 0, 4.0);
set_test_matrix_entry(&mut m, 2, 1, 5.0);
set_test_matrix_entry(&mut m, 2, 2, 6.0);
assert_eq!(
try_insphere_from_matrix(&m, k, 1).unwrap(),
InSphere::BOUNDARY
);
});
}
#[test]
fn test_insphere_matrix_rejects_nonfinite_entry_at_set_boundary() {
let k = 3;
with_la_stack_matrix!(k, |m| {
let err = try_matrix_set(&mut m, 2, 2, f64::NAN).unwrap_err();
assert_eq!(
err,
StackMatrixDispatchError::La {
source: LaError::non_finite_input_matrix(2, 2),
}
);
});
}
#[test]
fn test_try_insphere_from_matrix_rejects_dimension_mismatch() {
let matrix = Matrix::<3>::zero();
let err = try_insphere_from_matrix(&matrix, 2, 1).unwrap_err();
assert_matches!(
err,
StackMatrixDispatchError::ActiveBlockDimensionMismatch { k: 2, dim: 3 }
);
}
#[test]
fn test_insphere_wrong_point_count() {
let two_points: Vec<Point<3>> = 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 = insphere(
&two_points,
Point::try_new([0.5, 0.5, 0.5]).expect("finite point coordinates"),
);
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::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 far_point = Point::try_new([1e155, 0.0, 0.0]).expect("finite point coordinates");
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::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"),
];
assert_eq!(
simplex_orientation(&collinear_2d).unwrap(),
Orientation::DEGENERATE,
"Exactly collinear points must be DEGENERATE"
);
let coplanar_3d = vec![
Point::try_new([1.0, 2.0, 3.0]).expect("finite point coordinates"),
Point::try_new([4.0, 5.0, 6.0]).expect("finite point coordinates"),
Point::try_new([5.0, 7.0, 9.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
];
assert_eq!(
simplex_orientation(&coplanar_3d).unwrap(),
Orientation::DEGENERATE,
"Linearly dependent 3D simplex must be DEGENERATE"
);
}
}