#![forbid(unsafe_code)]
use super::conversions::{safe_coords_to_f64, safe_scalar_from_f64, safe_scalar_to_f64};
use super::norms::{hypot, squared_norm};
use crate::geometry::matrix::matrix_set;
use crate::geometry::point::Point;
use crate::geometry::traits::coordinate::{Coordinate, ScalarSummable};
use la_stack::{DEFAULT_PIVOT_TOL, LaError, Vector as LaVector};
pub use super::CircumcenterError;
pub fn circumcenter<T, const D: usize>(
points: &[Point<T, D>],
) -> Result<Point<T, D>, CircumcenterError>
where
T: ScalarSummable,
{
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_UNUSED_IMPORTS").is_some() {
eprintln!(
"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 = crate::geometry::matrix::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 = [T::zero(); D];
for j in 0..D {
diff_coords[j] = coords_point[j] - coords_0[j];
}
let squared_distance = squared_norm(&diff_coords);
let squared_distance_f64: f64 = safe_scalar_to_f64(squared_distance)?;
b_arr[i] = squared_distance_f64;
}
let lu = match a.lu(DEFAULT_PIVOT_TOL) {
Ok(lu) => lu,
Err(LaError::Singular { .. }) => {
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_LU_FALLBACK").is_some() {
eprintln!(
"circumcenter<{D}>: LU factorization fell back to zero pivot tolerance (DEFAULT_PIVOT_TOL={DEFAULT_PIVOT_TOL})",
);
}
a.lu(0.0)
.map_err(|e| CircumcenterError::MatrixInversionFailed {
details: format!("LU factorization failed: {e}"),
})?
}
Err(e) => {
return Err(CircumcenterError::MatrixInversionFailed {
details: format!("LU factorization failed: {e}"),
});
}
};
let x = lu
.solve_vec(LaVector::<D>::new(b_arr))
.map_err(|e| CircumcenterError::MatrixInversionFailed {
details: format!("LU solve failed: {e}"),
})?
.into_array();
let mut circumcenter_coords = [T::zero(); D];
for i in 0..D {
let relative_coord: T = safe_scalar_from_f64(0.5 * x[i])?;
circumcenter_coords[i] = coords_0[i] + relative_coord;
}
Ok(Point::new(circumcenter_coords))
}
pub fn circumradius<T, const D: usize>(points: &[Point<T, D>]) -> Result<T, CircumcenterError>
where
T: ScalarSummable,
{
let circumcenter = circumcenter(points)?;
circumradius_with_center(points, &circumcenter)
}
pub fn circumradius_with_center<T, const D: usize>(
points: &[Point<T, D>],
circumcenter: &Point<T, D>,
) -> Result<T, CircumcenterError>
where
T: ScalarSummable,
{
if points.is_empty() {
return Err(CircumcenterError::EmptyPointSet);
}
let point_coords = points[0].coords();
let circumcenter_coords = circumcenter.coords();
let mut diff_coords = [T::zero(); 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 approx::assert_relative_eq;
#[test]
fn predicates_circumcenter() {
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();
assert_eq!(center, Point::new([0.5, 0.5, 0.5]));
}
#[test]
fn predicates_circumcenter_fail() {
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]),
];
let center = circumcenter(&points);
assert!(center.is_err());
}
#[test]
fn predicates_circumradius() {
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 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::new([0.0, 0.0]),
Point::new([2.0, 0.0]),
Point::new([1.0, 2.0]),
];
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 predicates_circumradius_2d() {
let points = vec![
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
];
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::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 test_circumcenter_regular_simplex_3d() {
let points = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.5, 3.0_f64.sqrt() / 2.0, 0.0]),
Point::new([0.5, 3.0_f64.sqrt() / 6.0, (2.0 / 3.0_f64).sqrt()]),
];
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<f64, 4>> = 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 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::new([0.0, 0.0]),
Point::new([4.0, 0.0]),
Point::new([0.0, 3.0]),
];
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::new([0.0 * scale, 0.0 * scale, 0.0 * scale]),
Point::new([1.0 * scale, 0.0 * scale, 0.0 * scale]),
Point::new([0.0 * scale, 1.0 * scale, 0.0 * scale]),
Point::new([0.0 * scale, 0.0 * scale, 1.0 * scale]),
];
let center = circumcenter(&points).unwrap();
let expected_center = Point::new([0.5 * scale, 0.5 * scale, 0.5 * scale]);
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::new([
0.0 + translation[0],
0.0 + translation[1],
0.0 + translation[2],
]),
Point::new([
1.0 + translation[0],
0.0 + translation[1],
0.0 + translation[2],
]),
Point::new([
0.0 + translation[0],
1.0 + translation[1],
0.0 + translation[2],
]),
Point::new([
0.0 + translation[0],
0.0 + translation[1],
1.0 + translation[2],
]),
];
let center = circumcenter(&points).unwrap();
let untranslated_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 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<f64, 3>> = vec![
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.5, eps, 0.0]), Point::new([0.5, 0.0, eps]), ];
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<f64, 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::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])];
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::new([0.0, 0.0]),
Point::new([side_length, 0.0]),
Point::new([side_length / 2.0, height]),
];
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::new([center_coords[0], center_coords[1]]);
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<f64, 2>> = vec![
Point::new([1.0, 0.0]),
Point::new([1.000_000_1, 0.0]), Point::new([1.000_000_1, 0.000_000_1]), ];
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::new([0.0]), Point::new([2.0])];
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<f64, 5>> = 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 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::new([0.0, 0.0, 0.0]),
Point::new([6.0, 0.0, 0.0]),
Point::new([0.0, 8.0, 0.0]),
Point::new([0.0, 0.0, 10.0]),
];
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<f64, 3>> = vec![];
let result = circumcenter(&empty_points);
assert!(matches!(result, Err(CircumcenterError::EmptyPointSet)));
}
#[test]
fn test_circumcenter_invalid_simplex() {
let points_2d = vec![
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
];
let result = circumcenter(&points_2d);
assert!(matches!(
result,
Err(CircumcenterError::InvalidSimplex { .. })
));
let points_extra = vec![
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
Point::new([0.5, 0.5]), ];
let result = circumcenter(&points_extra);
assert!(matches!(
result,
Err(CircumcenterError::InvalidSimplex { .. })
));
}
#[test]
fn test_circumcenter_degenerate_matrix() {
let collinear_points = vec![
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([2.0, 0.0]), ];
let result = circumcenter(&collinear_points);
assert!(matches!(
result,
Err(CircumcenterError::MatrixInversionFailed { .. })
));
}
}