#![forbid(unsafe_code)]
use crate::geometry::point::Point;
use crate::geometry::predicates::{InSphere, Orientation, insphere_lifted, simplex_orientation};
use crate::geometry::robust_predicates::{
RobustPredicateConfig, config_presets, robust_insphere, robust_orientation,
};
use crate::geometry::traits::coordinate::{
CoordinateConversionError, CoordinateScalar, ScalarSummable,
};
use core::marker::PhantomData;
pub trait Kernel<const D: usize>: Clone + Default {
type Scalar: CoordinateScalar;
fn orientation(
&self,
points: &[Point<Self::Scalar, D>],
) -> Result<i32, CoordinateConversionError>;
fn in_sphere(
&self,
simplex_points: &[Point<Self::Scalar, D>],
test_point: &Point<Self::Scalar, D>,
) -> Result<i32, CoordinateConversionError>;
}
#[derive(Clone, Default, Debug)]
pub struct FastKernel<T: CoordinateScalar> {
_phantom: PhantomData<T>,
}
impl<T: CoordinateScalar> FastKernel<T> {
#[must_use]
pub const fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<T, const D: usize> Kernel<D> for FastKernel<T>
where
T: ScalarSummable,
{
type Scalar = T;
fn orientation(
&self,
points: &[Point<Self::Scalar, D>],
) -> Result<i32, CoordinateConversionError> {
let result = simplex_orientation(points)?;
Ok(match result {
Orientation::NEGATIVE => -1,
Orientation::DEGENERATE => 0,
Orientation::POSITIVE => 1,
})
}
fn in_sphere(
&self,
simplex_points: &[Point<Self::Scalar, D>],
test_point: &Point<Self::Scalar, D>,
) -> Result<i32, CoordinateConversionError> {
let result = insphere_lifted(simplex_points, *test_point).map_err(|e| {
match e {
crate::core::cell::CellValidationError::CoordinateConversion { source } => source,
_ => CoordinateConversionError::ConversionFailed {
coordinate_index: 0,
coordinate_value: format!("{e}"),
from_type: "insphere_lifted",
to_type: "in_sphere",
},
}
})?;
Ok(match result {
InSphere::OUTSIDE => -1,
InSphere::BOUNDARY => 0,
InSphere::INSIDE => 1,
})
}
}
#[derive(Clone, Debug)]
pub struct RobustKernel<T: CoordinateScalar> {
config: RobustPredicateConfig<T>,
_phantom: PhantomData<T>,
}
impl<T: CoordinateScalar> RobustKernel<T> {
#[must_use]
pub fn new() -> Self {
Self {
config: config_presets::general_triangulation(),
_phantom: PhantomData,
}
}
#[must_use]
pub const fn with_config(config: RobustPredicateConfig<T>) -> Self {
Self {
config,
_phantom: PhantomData,
}
}
}
impl<T: CoordinateScalar> Default for RobustKernel<T> {
fn default() -> Self {
Self::new()
}
}
impl<T, const D: usize> Kernel<D> for RobustKernel<T>
where
T: ScalarSummable,
{
type Scalar = T;
fn orientation(
&self,
points: &[Point<Self::Scalar, D>],
) -> Result<i32, CoordinateConversionError> {
let result = robust_orientation(points, &self.config)?;
Ok(match result {
Orientation::NEGATIVE => -1,
Orientation::DEGENERATE => 0,
Orientation::POSITIVE => 1,
})
}
fn in_sphere(
&self,
simplex_points: &[Point<Self::Scalar, D>],
test_point: &Point<Self::Scalar, D>,
) -> Result<i32, CoordinateConversionError> {
let result = robust_insphere(simplex_points, test_point, &self.config)?;
Ok(match result {
InSphere::OUTSIDE => -1,
InSphere::BOUNDARY => 0,
InSphere::INSIDE => 1,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::point::Point;
use crate::geometry::traits::coordinate::Coordinate;
#[test]
fn test_fast_kernel_orientation_3d() {
let kernel = FastKernel::<f64>::new();
let points = [
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 orientation = kernel.orientation(&points).unwrap();
assert!(orientation == -1 || orientation == 1);
}
#[test]
fn test_fast_kernel_in_sphere_3d() {
let kernel = FastKernel::<f64>::new();
let simplex = [
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 inside_point = Point::new([0.25, 0.25, 0.25]);
let result = kernel.in_sphere(&simplex, &inside_point).unwrap();
assert_eq!(result, 1);
let outside_point = Point::new([2.0, 2.0, 2.0]);
let result = kernel.in_sphere(&simplex, &outside_point).unwrap();
assert_eq!(result, -1); }
#[test]
fn test_robust_kernel_orientation_3d() {
let kernel = RobustKernel::<f64>::new();
let points = [
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 orientation = kernel.orientation(&points).unwrap();
assert!(orientation == -1 || orientation == 1);
}
#[test]
fn test_robust_kernel_in_sphere_3d() {
let kernel = RobustKernel::<f64>::new();
let simplex = [
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 inside_point = Point::new([0.25, 0.25, 0.25]);
let result = kernel.in_sphere(&simplex, &inside_point).unwrap();
assert_eq!(result, 1);
let outside_point = Point::new([2.0, 2.0, 2.0]);
let result = kernel.in_sphere(&simplex, &outside_point).unwrap();
assert_eq!(result, -1); }
#[test]
fn test_kernel_consistency_fast_vs_robust() {
let fast_kernel = FastKernel::<f64>::new();
let robust_kernel = RobustKernel::<f64>::new();
let simplex = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.5, 1.0]),
];
let fast_orientation = fast_kernel.orientation(&simplex).unwrap();
let robust_orientation = robust_kernel.orientation(&simplex).unwrap();
assert_eq!(fast_orientation, robust_orientation);
let test_point = Point::new([0.5, 0.3]);
let fast_result = fast_kernel.in_sphere(&simplex, &test_point).unwrap();
let robust_result = robust_kernel.in_sphere(&simplex, &test_point).unwrap();
assert_eq!(fast_result, robust_result);
}
#[test]
fn test_fast_kernel_2d() {
let kernel = FastKernel::<f64>::new();
let points = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.5, 1.0]),
];
let orientation = kernel.orientation(&points).unwrap();
assert!(orientation != 0);
let inside = Point::new([0.5, 0.3]);
let result = kernel.in_sphere(&points, &inside).unwrap();
assert_eq!(result, 1); }
#[test]
fn test_robust_kernel_with_custom_config() {
let config = config_presets::high_precision();
let kernel = RobustKernel::<f64>::with_config(config);
let points = [
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 orientation = kernel.orientation(&points).unwrap();
assert!(orientation != 0); }
#[test]
fn test_orientation_collinear_2d_fast() {
let kernel = FastKernel::<f64>::new();
let collinear = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([2.0, 0.0]),
];
let orientation = kernel.orientation(&collinear).unwrap();
assert_eq!(
orientation, 0,
"Collinear points should have zero orientation"
);
}
#[test]
fn test_orientation_collinear_2d_robust() {
let kernel = RobustKernel::<f64>::new();
let collinear = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([2.0, 0.0]),
];
let orientation = kernel.orientation(&collinear).unwrap();
assert_eq!(
orientation, 0,
"Collinear points should have zero orientation"
);
}
#[test]
fn test_orientation_collinear_diagonal_2d() {
let kernel = FastKernel::<f64>::new();
let collinear = [
Point::new([0.0, 0.0]),
Point::new([1.0, 1.0]),
Point::new([2.0, 2.0]),
];
let orientation = kernel.orientation(&collinear).unwrap();
assert_eq!(
orientation, 0,
"Diagonal collinear points should have zero orientation"
);
}
#[test]
fn test_orientation_valid_triangle_2d() {
let kernel = FastKernel::<f64>::new();
let triangle = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.5, 0.866]), ];
let orientation = kernel.orientation(&triangle).unwrap();
assert_ne!(
orientation, 0,
"Valid triangle should have non-zero orientation"
);
assert!(
orientation == 1 || orientation == -1,
"Orientation should be +1 or -1"
);
}
#[test]
fn test_orientation_coplanar_3d_fast() {
let kernel = FastKernel::<f64>::new();
let coplanar = [
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.5, 0.5, 0.0]), ];
let orientation = kernel.orientation(&coplanar).unwrap();
assert_eq!(
orientation, 0,
"Coplanar points should have zero orientation"
);
}
#[test]
fn test_orientation_coplanar_3d_robust() {
let kernel = RobustKernel::<f64>::new();
let coplanar = [
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.5, 0.5, 0.0]), ];
let orientation = kernel.orientation(&coplanar).unwrap();
assert_eq!(
orientation, 0,
"Coplanar points should have zero orientation"
);
}
#[test]
fn test_orientation_valid_tetrahedron_3d() {
let kernel = FastKernel::<f64>::new();
let tetrahedron = [
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 orientation = kernel.orientation(&tetrahedron).unwrap();
assert_ne!(
orientation, 0,
"Valid tetrahedron should have non-zero orientation"
);
assert!(
orientation == 1 || orientation == -1,
"Orientation should be +1 or -1"
);
}
#[test]
fn test_orientation_nearly_collinear_2d_robust() {
let kernel = RobustKernel::<f64>::new();
let nearly_collinear = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([2.0, 1e-10]), ];
let orientation = kernel.orientation(&nearly_collinear).unwrap();
assert!(orientation == -1 || orientation == 0 || orientation == 1);
}
#[test]
fn test_orientation_extreme_coordinates_2d() {
let kernel = RobustKernel::<f64>::new();
let large_triangle = [
Point::new([1e6, 1e6]),
Point::new([1e6 + 1.0, 1e6]),
Point::new([1e6, 1e6 + 1.0]),
];
let orientation = kernel.orientation(&large_triangle).unwrap();
assert_ne!(
orientation, 0,
"Triangle with large coordinates should be non-degenerate"
);
}
#[test]
fn test_orientation_4d_valid_simplex() {
let kernel = FastKernel::<f64>::new();
let simplex_4d = [
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 orientation = kernel.orientation(&simplex_4d).unwrap();
assert_ne!(
orientation, 0,
"4D simplex should have non-zero orientation"
);
}
#[test]
fn test_orientation_4d_degenerate() {
let kernel = FastKernel::<f64>::new();
let degenerate_4d = [
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.5, 0.5, 0.5, 0.0]), ];
let orientation = kernel.orientation(°enerate_4d).unwrap();
assert_eq!(
orientation, 0,
"Degenerate 4D simplex should have zero orientation"
);
}
#[test]
fn test_orientation_small_but_valid_2d() {
let kernel = FastKernel::<f64>::new();
let small_triangle = [
Point::new([0.0, 0.0]),
Point::new([1e-6, 0.0]),
Point::new([0.0, 1e-6]),
];
let orientation = kernel.orientation(&small_triangle).unwrap();
assert_ne!(
orientation, 0,
"Small but valid triangle should be non-degenerate"
);
}
#[test]
fn test_orientation_consistency_both_kernels() {
let fast = FastKernel::<f64>::new();
let robust = RobustKernel::<f64>::new();
let collinear = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([2.0, 0.0]),
];
assert_eq!(
fast.orientation(&collinear).unwrap(),
robust.orientation(&collinear).unwrap(),
"Both kernels should agree on collinear points"
);
let triangle1 = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
];
assert_eq!(
fast.orientation(&triangle1).unwrap(),
robust.orientation(&triangle1).unwrap(),
"Both kernels should agree on valid triangle"
);
let triangle2 = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.5, 0.5]),
];
assert_eq!(
fast.orientation(&triangle2).unwrap(),
robust.orientation(&triangle2).unwrap(),
"Both kernels should agree on another valid triangle"
);
}
#[test]
fn test_kernel_default_trait() {
let _fast: FastKernel<f64> = FastKernel::default();
let _robust: RobustKernel<f64> = RobustKernel::default();
}
#[test]
fn test_fast_kernel_in_sphere_insufficient_vertices() {
let kernel = FastKernel::<f64>::new();
let simplex: [Point<f64, 3>; 2] =
[Point::new([0.0, 0.0, 0.0]), Point::new([1.0, 0.0, 0.0])];
let test_point = Point::new([0.5, 0.5, 0.5]);
let result = kernel.in_sphere(&simplex, &test_point);
assert!(result.is_err(), "Should error with insufficient vertices");
}
#[test]
fn test_fast_kernel_in_sphere_degenerate_simplex() {
let kernel = FastKernel::<f64>::new();
let simplex = [
Point::new([0.0, 0.0, 0.0]),
Point::new([1.0, 0.0, 0.0]),
Point::new([0.0, 1.0, 0.0]),
Point::new([1.0, 1.0, 0.0]), ];
let test_point = Point::new([0.5, 0.5, 0.5]);
let result = kernel.in_sphere(&simplex, &test_point);
assert!(result.is_err(), "Should error with degenerate simplex");
}
}