#![forbid(unsafe_code)]
use super::conversions::{ValueConversionError, safe_coords_to_f64};
use super::norms::{hypot, squared_norm};
use crate::geometry::matrix::{
DEFAULT_SINGULAR_TOL, LaError, LaVector, Matrix, MatrixError, SingularityReason,
StackMatrixDispatchError, matrix_set,
};
use crate::geometry::point::Point;
use crate::geometry::traits::coordinate::{
CoordinateConversionError, CoordinateConversionValue, CoordinateValidationError,
};
use core::{fmt, hint::cold_path};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum DegenerateMeasure {
Length,
Area,
Volume,
SurfaceArea,
}
impl fmt::Display for DegenerateMeasure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Length => f.write_str("length"),
Self::Area => f.write_str("area"),
Self::Volume => f.write_str("volume"),
Self::SurfaceArea => f.write_str("surface area"),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum DegenerateGeometry {
CoincidentPoints,
CollinearPoints,
CoplanarPoints,
CollinearOrCoplanarPoints,
}
impl fmt::Display for DegenerateGeometry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CoincidentPoints => f.write_str("coincident points"),
Self::CollinearPoints => f.write_str("collinear points"),
Self::CoplanarPoints => f.write_str("coplanar points"),
Self::CollinearOrCoplanarPoints => f.write_str("collinear or coplanar points"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum CircumcenterFailureReason {
DegenerateSimplex {
measure: DegenerateMeasure,
degeneracy: DegenerateGeometry,
},
DegenerateFacet {
measure: DegenerateMeasure,
degeneracy: DegenerateGeometry,
},
NonFiniteGramDeterminant,
NegativeGramDeterminant,
NonPositiveSimplexMeasure {
measure: DegenerateMeasure,
value: CoordinateConversionValue,
},
NonFiniteMeasure {
measure: DegenerateMeasure,
value: CoordinateConversionValue,
},
}
impl fmt::Display for CircumcenterFailureReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DegenerateSimplex {
measure,
degeneracy,
} => write!(f, "degenerate simplex with zero {measure} ({degeneracy})"),
Self::DegenerateFacet {
measure,
degeneracy,
} => write!(f, "degenerate facet with zero {measure} ({degeneracy})"),
Self::NonFiniteGramDeterminant => f.write_str("Gram determinant is non-finite"),
Self::NegativeGramDeterminant => {
f.write_str("Gram matrix has negative determinant (degenerate simplex)")
}
Self::NonPositiveSimplexMeasure { measure, value } => {
write!(f, "degenerate simplex with {measure} ≈ {value}")
}
Self::NonFiniteMeasure { measure, value } => {
write!(f, "{measure} calculation produced non-finite value {value}")
}
}
}
}
#[derive(Clone, Copy, Debug, thiserror::Error, Eq, PartialEq)]
#[non_exhaustive]
pub enum ArrayConversionFailureReason {
#[error("array length mismatch")]
LengthMismatch,
}
#[derive(Clone, Debug, thiserror::Error, PartialEq)]
#[non_exhaustive]
pub enum CircumcenterError {
#[error("Empty point set")]
EmptyPointSet,
#[error(
"Points do not form a valid simplex: expected {expected} points for dimension {dimension}, got {actual}"
)]
InvalidSimplex {
actual: usize,
expected: usize,
dimension: usize,
},
#[error("Matrix inversion failed: {reason}")]
MatrixInversionFailed {
reason: CircumcenterFailureReason,
},
#[error("Unsupported stack matrix dimension {requested} (maximum supported is {max})")]
UnsupportedMatrixDimension {
requested: usize,
max: usize,
},
#[error(
"Active matrix block size {active} does not match concrete matrix dimension {matrix_dimension}"
)]
MatrixDimensionMismatch {
active: usize,
matrix_dimension: usize,
},
#[error("Linear algebra failure: {source}")]
LinearAlgebraFailure {
#[source]
source: LaError,
},
#[error("Matrix error: {source}")]
MatrixError {
#[from]
source: MatrixError,
},
#[error("Array conversion failed: {reason}")]
ArrayConversionFailed {
reason: ArrayConversionFailureReason,
},
#[error("Coordinate conversion error: {source}")]
CoordinateConversion {
#[from]
source: CoordinateConversionError,
},
#[error("Coordinate validation error: {source}")]
CoordinateValidation {
#[from]
source: CoordinateValidationError,
},
#[error("Value conversion error: {source}")]
ValueConversion {
#[source]
source: Box<ValueConversionError>,
},
}
impl From<ValueConversionError> for CircumcenterError {
fn from(source: ValueConversionError) -> Self {
Self::ValueConversion {
source: Box::new(source),
}
}
}
impl From<StackMatrixDispatchError> for CircumcenterError {
fn from(source: StackMatrixDispatchError) -> Self {
match source {
StackMatrixDispatchError::UnsupportedDim { k, max } => {
Self::UnsupportedMatrixDimension { requested: k, max }
}
StackMatrixDispatchError::ActiveBlockDimensionMismatch { k, dim } => {
Self::MatrixDimensionMismatch {
active: k,
matrix_dimension: dim,
}
}
StackMatrixDispatchError::La { source } => Self::LinearAlgebraFailure { source },
StackMatrixDispatchError::Matrix { source } => Self::MatrixError { source },
}
}
}
impl From<LaError> for CircumcenterError {
fn from(source: LaError) -> Self {
Self::from(StackMatrixDispatchError::from(source))
}
}
pub fn circumcenter<const D: usize>(points: &[Point<D>]) -> Result<Point<D>, CircumcenterError> {
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_UNUSED_IMPORTS").is_some() {
tracing::debug!(
"circumsphere::circumcenter called (points_len={}, D={})",
points.len(),
D
);
}
if points.is_empty() {
return Err(CircumcenterError::EmptyPointSet);
}
let dim = points.len() - 1;
if dim != D {
return Err(CircumcenterError::InvalidSimplex {
actual: points.len(),
expected: D + 1,
dimension: D,
});
}
let coords_0 = points[0].coords();
let coords_0_f64: [f64; D] = safe_coords_to_f64(coords_0)?;
let mut a = Matrix::<D>::zero();
let mut b_arr = [0.0f64; D];
for i in 0..D {
let coords_point = points[i + 1].coords();
let coords_point_f64: [f64; D] = safe_coords_to_f64(coords_point)?;
for j in 0..D {
matrix_set(&mut a, i, j, coords_point_f64[j] - coords_0_f64[j])?;
}
let mut diff_coords = [0.0; D];
for j in 0..D {
diff_coords[j] = coords_point_f64[j] - coords_0_f64[j];
}
b_arr[i] = squared_norm(&diff_coords);
}
let b_vec = LaVector::<D>::try_new(b_arr)?;
let x = match a.lu(DEFAULT_SINGULAR_TOL) {
Ok(lu) => lu
.solve(b_vec)
.map_err(CircumcenterError::from)?
.into_array(),
Err(LaError::Singular {
reason: SingularityReason::Numerical { .. },
..
}) => {
cold_path();
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_LU_FALLBACK").is_some() {
tracing::debug!(
"circumcenter<{D}>: LU near-singular, using solve_exact_rounded_f64"
);
}
a.solve_exact_rounded_f64(b_vec)
.map_err(CircumcenterError::from)?
.into_array()
}
Err(e) => {
cold_path();
return Err(e.into());
}
};
let mut circumcenter_coords = [0.0; D];
for i in 0..D {
circumcenter_coords[i] = 0.5_f64.mul_add(x[i], coords_0_f64[i]);
}
for value in circumcenter_coords {
if !value.is_finite() {
return Err(CircumcenterError::MatrixInversionFailed {
reason: CircumcenterFailureReason::NonFiniteMeasure {
measure: DegenerateMeasure::Volume,
value: CoordinateConversionValue::from_numeric_debug(&value),
},
});
}
}
Ok(Point::try_new(circumcenter_coords)?)
}
pub fn circumradius<const D: usize>(points: &[Point<D>]) -> Result<f64, CircumcenterError> {
let circumcenter = circumcenter(points)?;
circumradius_with_center(points, &circumcenter)
}
pub fn circumradius_with_center<const D: usize>(
points: &[Point<D>],
circumcenter: &Point<D>,
) -> Result<f64, CircumcenterError> {
if points.is_empty() {
return Err(CircumcenterError::EmptyPointSet);
}
let point_coords = points[0].coords();
let circumcenter_coords = circumcenter.coords();
let mut diff_coords = [0.0; D];
for i in 0..D {
diff_coords[i] = circumcenter_coords[i] - point_coords[i];
}
let distance = hypot(&diff_coords);
Ok(distance)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::point::Point;
use crate::geometry::util::conversions::ValueConversionFailureReason;
use approx::assert_relative_eq;
use std::assert_matches;
#[test]
fn circumcenter_error_display_names_variants() {
let empty_error = CircumcenterError::EmptyPointSet;
let display = format!("{empty_error}");
assert!(display.contains("Empty point set"));
let simplex_error = CircumcenterError::InvalidSimplex {
actual: 2,
expected: 3,
dimension: 2,
};
let display = format!("{simplex_error}");
assert!(display.contains("Points do not form a valid simplex"));
}
#[test]
fn degenerate_measure_display_names_all_variants() {
assert_eq!(DegenerateMeasure::Length.to_string(), "length");
assert_eq!(DegenerateMeasure::Area.to_string(), "area");
assert_eq!(DegenerateMeasure::Volume.to_string(), "volume");
assert_eq!(DegenerateMeasure::SurfaceArea.to_string(), "surface area");
}
#[test]
fn degenerate_geometry_display_names_all_variants() {
assert_eq!(
DegenerateGeometry::CoincidentPoints.to_string(),
"coincident points"
);
assert_eq!(
DegenerateGeometry::CollinearPoints.to_string(),
"collinear points"
);
assert_eq!(
DegenerateGeometry::CoplanarPoints.to_string(),
"coplanar points"
);
assert_eq!(
DegenerateGeometry::CollinearOrCoplanarPoints.to_string(),
"collinear or coplanar points"
);
}
#[test]
fn circumcenter_failure_reason_display_preserves_typed_payloads() {
let degenerate_simplex = CircumcenterFailureReason::DegenerateSimplex {
measure: DegenerateMeasure::Volume,
degeneracy: DegenerateGeometry::CoplanarPoints,
};
assert_eq!(
degenerate_simplex.to_string(),
"degenerate simplex with zero volume (coplanar points)"
);
let degenerate_facet = CircumcenterFailureReason::DegenerateFacet {
measure: DegenerateMeasure::Length,
degeneracy: DegenerateGeometry::CoincidentPoints,
};
assert_eq!(
degenerate_facet.to_string(),
"degenerate facet with zero length (coincident points)"
);
assert_eq!(
CircumcenterFailureReason::NonFiniteGramDeterminant.to_string(),
"Gram determinant is non-finite"
);
assert_eq!(
CircumcenterFailureReason::NegativeGramDeterminant.to_string(),
"Gram matrix has negative determinant (degenerate simplex)"
);
assert_eq!(
CircumcenterFailureReason::NonPositiveSimplexMeasure {
measure: DegenerateMeasure::SurfaceArea,
value: CoordinateConversionValue::from_f64(0.0),
}
.to_string(),
"degenerate simplex with surface area ≈ 0.0"
);
assert_eq!(
CircumcenterFailureReason::NonFiniteMeasure {
measure: DegenerateMeasure::Volume,
value: CoordinateConversionValue::from_f64(f64::INFINITY),
}
.to_string(),
"volume calculation produced non-finite value inf"
);
}
#[test]
fn circumcenter_error_conversions_preserve_typed_payloads() {
let value_error = ValueConversionError::ConversionFailed {
value: CoordinateConversionValue::from_usize(4),
from_type: "usize",
to_type: "f64",
reason: ValueConversionFailureReason::TargetTypeRejected,
};
assert_matches!(
CircumcenterError::from(value_error),
CircumcenterError::ValueConversion { source }
if matches!(
*source,
ValueConversionError::ConversionFailed {
value: CoordinateConversionValue::UnsignedInteger(4),
from_type: "usize",
to_type: "f64",
reason: ValueConversionFailureReason::TargetTypeRejected,
}
)
);
assert_eq!(
CircumcenterError::from(StackMatrixDispatchError::ActiveBlockDimensionMismatch {
k: 4,
dim: 3,
}),
CircumcenterError::MatrixDimensionMismatch {
active: 4,
matrix_dimension: 3,
}
);
}
#[test]
fn predicates_circumcenter() {
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([0.0, 1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"),
];
let center = circumcenter(&points).unwrap();
assert_eq!(
center,
Point::try_new([0.5, 0.5, 0.5]).expect("finite point coordinates")
);
}
#[test]
fn predicates_circumcenter_fail() {
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([0.0, 1.0, 0.0]).expect("finite point coordinates"),
];
let center = circumcenter(&points);
assert!(center.is_err());
}
#[test]
fn predicates_circumradius() {
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([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 = circumradius(&points).unwrap();
let expected_radius: f64 = 3.0_f64.sqrt() / 2.0;
assert_relative_eq!(radius, expected_radius, epsilon = 1e-9);
}
#[test]
fn predicates_circumcenter_2d() {
let points = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([2.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 2.0]).expect("finite point coordinates"),
];
let center = circumcenter(&points).unwrap();
assert_relative_eq!(center.coords()[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(center.coords()[1], 0.75, epsilon = 1e-10);
}
#[test]
fn test_circumradius_with_center_empty_point_set() {
let points: Vec<Point<3>> = Vec::new();
let center = Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates");
match circumradius_with_center(&points, ¢er) {
Err(CircumcenterError::EmptyPointSet) => {}
other => panic!("expected EmptyPointSet, got {other:?}"),
}
}
#[test]
fn predicates_circumradius_2d() {
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"),
Point::try_new([0.0, 1.0]).expect("finite point coordinates"),
];
let radius = circumradius(&points).unwrap();
let expected_radius = 2.0_f64.sqrt() / 2.0;
assert_relative_eq!(radius, expected_radius, epsilon = 1e-10);
}
#[test]
fn predicates_circumradius_with_center() {
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([0.0, 1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 1.0]).expect("finite point coordinates"),
];
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 test_circumcenter_regular_simplex_3d() {
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([0.5, 3.0_f64.sqrt() / 2.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.5, 3.0_f64.sqrt() / 6.0, (2.0 / 3.0_f64).sqrt()])
.expect("finite point coordinates"),
];
let center = circumcenter(&points).unwrap();
let center_coords = center.coords();
for coord in center_coords {
assert!(
coord.is_finite(),
"Circumcenter coordinates should be finite"
);
}
let distances: Vec<f64> = points
.iter()
.map(|p| {
let p_coords = *p.coords();
let diff = [
p_coords[0] - center_coords[0],
p_coords[1] - center_coords[1],
p_coords[2] - center_coords[2],
];
hypot(&diff)
})
.collect();
for i in 1..distances.len() {
assert_relative_eq!(distances[0], distances[i], epsilon = 1e-10);
}
}
#[test]
fn test_circumcenter_regular_simplex_4d() {
let points: Vec<Point<4>> = 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 center = circumcenter(&points).unwrap();
let center_coords = center.coords();
for &coord in center_coords {
assert!(
coord.is_finite(),
"Circumcenter coordinates should be finite"
);
assert_relative_eq!(coord, 0.5, epsilon = 1e-9);
}
}
#[test]
fn test_circumcenter_right_triangle_2d() {
let points = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([4.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 3.0]).expect("finite point coordinates"),
];
let center = circumcenter(&points).unwrap();
let center_coords = center.coords();
assert_relative_eq!(center_coords[0], 2.0, epsilon = 1e-10);
assert_relative_eq!(center_coords[1], 1.5, epsilon = 1e-10);
}
#[test]
fn test_circumcenter_scaled_simplex() {
let scale = 10.0;
let points = vec![
Point::try_new([0.0 * scale, 0.0 * scale, 0.0 * scale])
.expect("finite point coordinates"),
Point::try_new([1.0 * scale, 0.0 * scale, 0.0 * scale])
.expect("finite point coordinates"),
Point::try_new([0.0 * scale, 1.0 * scale, 0.0 * scale])
.expect("finite point coordinates"),
Point::try_new([0.0 * scale, 0.0 * scale, 1.0 * scale])
.expect("finite point coordinates"),
];
let center = circumcenter(&points).unwrap();
let expected_center = Point::try_new([0.5 * scale, 0.5 * scale, 0.5 * scale])
.expect("finite point coordinates");
let center_coords = center.coords();
let expected_coords = expected_center.coords();
for i in 0..3 {
assert_relative_eq!(center_coords[i], expected_coords[i], epsilon = 1e-9);
}
}
#[test]
fn test_circumcenter_translated_simplex() {
let translation = [10.0, 20.0, 30.0];
let points = vec![
Point::try_new([
0.0 + translation[0],
0.0 + translation[1],
0.0 + translation[2],
])
.expect("finite point coordinates"),
Point::try_new([
1.0 + translation[0],
0.0 + translation[1],
0.0 + translation[2],
])
.expect("finite point coordinates"),
Point::try_new([
0.0 + translation[0],
1.0 + translation[1],
0.0 + translation[2],
])
.expect("finite point coordinates"),
Point::try_new([
0.0 + translation[0],
0.0 + translation[1],
1.0 + translation[2],
])
.expect("finite point coordinates"),
];
let center = circumcenter(&points).unwrap();
let untranslated_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 untranslated_center = circumcenter(&untranslated_points).unwrap();
let center_coords = center.coords();
let untranslated_coords = untranslated_center.coords();
for i in 0..3 {
assert_relative_eq!(
center_coords[i],
untranslated_coords[i] + translation[i],
epsilon = 1e-9
);
}
let expected = [10.5, 20.5, 30.5];
for i in 0..3 {
assert_relative_eq!(center_coords[i], expected[i], epsilon = 1e-9);
}
}
#[test]
fn test_circumcenter_nearly_degenerate_simplex() {
let eps = 1e-3; let 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"),
Point::try_new([0.5, eps, 0.0]).expect("finite point coordinates"), Point::try_new([0.5, 0.0, eps]).expect("finite point coordinates"), ];
let result = circumcenter(&points);
if let Ok(center) = result {
let coords = center.coords();
assert!(
coords.iter().all(|&x| x.is_finite()),
"Circumcenter coordinates should be finite"
);
} else {
}
}
#[test]
fn test_circumcenter_empty_points() {
let points: Vec<Point<3>> = vec![];
let result = circumcenter(&points);
assert!(result.is_err());
match result.unwrap_err() {
CircumcenterError::EmptyPointSet => {}
other => panic!("Expected EmptyPointSet error, got: {other:?}"),
}
}
#[test]
fn test_circumcenter_wrong_dimension() {
let points = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0, 0.0]).expect("finite point coordinates"),
];
let result = circumcenter(&points);
assert!(result.is_err());
match result.unwrap_err() {
CircumcenterError::InvalidSimplex {
actual,
expected,
dimension,
} => {
assert_eq!(actual, 2);
assert_eq!(expected, 4); assert_eq!(dimension, 3);
}
other => panic!("Expected InvalidSimplex error, got: {other:?}"),
}
}
#[test]
fn test_circumcenter_equilateral_triangle_properties() {
let side_length = 2.0;
let height = side_length * 3.0_f64.sqrt() / 2.0;
let points = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([side_length, 0.0]).expect("finite point coordinates"),
Point::try_new([side_length / 2.0, height]).expect("finite point coordinates"),
];
let center = circumcenter(&points).unwrap();
let center_coords = center.coords();
let expected_x = side_length / 2.0;
let expected_y = height / 3.0;
assert_relative_eq!(center_coords[0], expected_x, epsilon = 1e-10);
assert_relative_eq!(center_coords[1], expected_y, epsilon = 1e-10);
let _center_point =
Point::try_new([center_coords[0], center_coords[1]]).expect("finite point coordinates");
let distances: Vec<f64> = points
.iter()
.map(|p| {
let p_coords = *p.coords();
let diff = [
p_coords[0] - center_coords[0],
p_coords[1] - center_coords[1],
];
hypot(&diff)
})
.collect();
for i in 1..distances.len() {
assert_relative_eq!(distances[0], distances[i], epsilon = 1e-10);
}
}
#[test]
fn test_circumcenter_numerical_stability() {
let points: Vec<Point<2>> = vec![
Point::try_new([1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.000_000_1, 0.0]).expect("finite point coordinates"), Point::try_new([1.000_000_1, 0.000_000_1]).expect("finite point coordinates"), ];
let result = circumcenter(&points);
if let Ok(center) = result {
let coords = center.coords();
assert!(
coords.iter().all(|&x| x.is_finite()),
"Circumcenter coordinates should be finite"
);
} else {
}
}
#[test]
fn test_circumcenter_1d_case() {
let points = vec![
Point::try_new([0.0]).expect("finite point coordinates"),
Point::try_new([2.0]).expect("finite point coordinates"),
];
let center = circumcenter(&points).unwrap();
let center_coords = center.coords();
assert_relative_eq!(center_coords[0], 1.0, epsilon = 1e-10);
}
#[test]
fn test_circumcenter_high_dimension() {
let points: Vec<Point<5>> = 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 result = circumcenter(&points);
assert!(result.is_ok(), "5D circumcenter should work");
let center = result.unwrap();
let center_coords = center.coords();
for coord in center_coords {
assert!(
coord.is_finite(),
"Circumcenter coordinates should be finite"
);
}
let distances: Vec<f64> = points
.iter()
.map(|p| {
let p_coords = *p.coords();
let diff = [
p_coords[0] - center_coords[0],
p_coords[1] - center_coords[1],
p_coords[2] - center_coords[2],
p_coords[3] - center_coords[3],
p_coords[4] - center_coords[4],
];
hypot(&diff)
})
.collect();
for i in 1..distances.len() {
assert_relative_eq!(distances[0], distances[i], epsilon = 1e-9);
}
}
#[test]
fn predicates_circumcenter_precise_values() {
let points = vec![
Point::try_new([0.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([6.0, 0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 8.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.0, 0.0, 10.0]).expect("finite point coordinates"),
];
let center = circumcenter(&points).unwrap();
let center_coords = center.coords();
assert_relative_eq!(center_coords[0], 3.0, epsilon = 1e-10);
assert_relative_eq!(center_coords[1], 4.0, epsilon = 1e-10);
assert_relative_eq!(center_coords[2], 5.0, epsilon = 1e-10);
}
#[test]
fn test_circumcenter_empty_point_set() {
let empty_points: Vec<Point<3>> = vec![];
let result = circumcenter(&empty_points);
assert_matches!(result, Err(CircumcenterError::EmptyPointSet));
}
#[test]
fn test_circumcenter_invalid_simplex() {
let points_2d = vec![
Point::try_new([0.0, 0.0]).expect("finite point coordinates"),
Point::try_new([1.0, 0.0]).expect("finite point coordinates"),
];
let result = circumcenter(&points_2d);
assert_matches!(result, Err(CircumcenterError::InvalidSimplex { .. }));
let points_extra = 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"),
Point::try_new([0.5, 0.5]).expect("finite point coordinates"), ];
let result = circumcenter(&points_extra);
assert_matches!(result, Err(CircumcenterError::InvalidSimplex { .. }));
}
#[test]
fn test_circumcenter_degenerate_matrix() {
let collinear_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([2.0, 0.0]).expect("finite point coordinates"), ];
let result = circumcenter(&collinear_points);
assert_matches!(
result,
Err(CircumcenterError::LinearAlgebraFailure {
source: LaError::Singular { .. }
})
);
}
#[test]
fn test_circumcenter_exact_fallback_near_singular_3d() {
let eps = 1e-14; let 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"),
Point::try_new([0.0, 1.0, 0.0]).expect("finite point coordinates"),
Point::try_new([0.5, 0.5, eps]).expect("finite point coordinates"), ];
let system = Matrix::try_from_rows([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.5, 0.5, eps]])
.expect("finite system matrix");
assert_matches!(
system.lu(DEFAULT_SINGULAR_TOL),
Err(LaError::Singular {
reason: SingularityReason::Numerical { .. },
..
})
);
let result = circumcenter(&points);
let center = result.expect("exact fallback should handle near-singular system");
let center_coords = center.coords();
assert!(
center_coords.iter().all(|&x| x.is_finite()),
"Circumcenter coordinates should be finite"
);
let distances: Vec<f64> = points
.iter()
.map(|p| {
let diff = [
p.coords()[0] - center_coords[0],
p.coords()[1] - center_coords[1],
p.coords()[2] - center_coords[2],
];
hypot(&diff)
})
.collect();
for i in 1..distances.len() {
assert_relative_eq!(distances[0], distances[i], epsilon = 1e-6);
}
}
#[test]
fn test_circumcenter_exact_fallback_near_singular_2d() {
let eps = 1e-15;
let points: Vec<Point<2>> = 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 result = circumcenter(&points);
let center = result.expect("exact fallback should handle near-singular 2D system");
let center_coords = center.coords();
assert!(
center_coords.iter().all(|&x| x.is_finite()),
"Circumcenter coordinates should be finite"
);
assert_relative_eq!(center_coords[0], 0.5, epsilon = 1e-6);
}
}