#![forbid(unsafe_code)]
use crate::core::collections::{MAX_PRACTICAL_DIMENSION_SIZE, SmallBuffer};
use crate::core::simplex::SimplexValidationError;
use crate::geometry::point::Point;
use crate::geometry::predicates::{
InSphere, Orientation, insphere_lifted, relative_insphere_determinant_sign,
relative_insphere_effective_sign, relative_insphere_signs, simplex_orientation,
};
use crate::geometry::robust_predicates::{
robust_insphere, robust_insphere_positive_oriented, robust_orientation,
};
use crate::geometry::sos::{sos_insphere_sign, sos_orientation_sign};
use crate::geometry::traits::coordinate::{
Coordinate, CoordinateConversionError, CoordinateScalar, DegenerateSimplexReason,
};
use crate::geometry::util::safe_coords_to_f64;
use core::marker::PhantomData;
#[inline]
const fn insphere_to_i32(result: InSphere) -> i32 {
match result {
InSphere::OUTSIDE => -1,
InSphere::BOUNDARY => 0,
InSphere::INSIDE => 1,
}
}
pub trait Kernel<const D: usize>: Clone {
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>;
fn in_sphere_positive_oriented(
&self,
simplex_points: &[Point<Self::Scalar, D>],
test_point: &Point<Self::Scalar, D>,
) -> Result<i32, CoordinateConversionError> {
self.in_sphere(simplex_points, test_point)
}
}
pub trait ExactPredicates<const D: usize>: Kernel<D> {}
macro_rules! impl_exact_predicates_for_supported_dims {
($($dim:literal),* $(,)?) => {
$(
impl<T: CoordinateScalar> ExactPredicates<$dim> for RobustKernel<T> {}
impl<T: CoordinateScalar> ExactPredicates<$dim> for AdaptiveKernel<T> {}
)*
};
}
impl_exact_predicates_for_supported_dims!(0, 1, 2, 3, 4, 5);
#[derive(Clone, Default, Debug)]
pub struct FastKernel<T> {
_phantom: PhantomData<T>,
}
impl<T> FastKernel<T> {
#[must_use]
pub const fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<T, const D: usize> Kernel<D> for FastKernel<T>
where
T: CoordinateScalar,
{
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 {
SimplexValidationError::CoordinateConversion { source } => source,
SimplexValidationError::InsufficientVertices {
actual,
expected,
dimension,
} => CoordinateConversionError::InvalidSimplexPointCount {
actual,
expected,
dimension,
},
SimplexValidationError::DegenerateSimplex => {
CoordinateConversionError::DegenerateSimplex {
dimension: D,
reason: DegenerateSimplexReason::ZeroOrientation,
}
}
_ => CoordinateConversionError::ConversionFailed {
coordinate_index: 0,
coordinate_value: format!("{e}"),
from_type: "insphere_lifted",
to_type: "in_sphere",
},
}
})?;
Ok(insphere_to_i32(result))
}
fn in_sphere_positive_oriented(
&self,
simplex_points: &[Point<Self::Scalar, D>],
test_point: &Point<Self::Scalar, D>,
) -> Result<i32, CoordinateConversionError> {
let result = robust_insphere_positive_oriented(simplex_points, test_point)?;
Ok(insphere_to_i32(result))
}
}
#[derive(Clone, Default, Debug)]
pub struct RobustKernel<T> {
_phantom: PhantomData<T>,
}
impl<T> RobustKernel<T> {
#[must_use]
pub const fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<T, const D: usize> Kernel<D> for RobustKernel<T>
where
T: CoordinateScalar,
{
type Scalar = T;
fn orientation(
&self,
points: &[Point<Self::Scalar, D>],
) -> Result<i32, CoordinateConversionError> {
let result = robust_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 = robust_insphere(simplex_points, test_point)?;
Ok(insphere_to_i32(result))
}
fn in_sphere_positive_oriented(
&self,
simplex_points: &[Point<Self::Scalar, D>],
test_point: &Point<Self::Scalar, D>,
) -> Result<i32, CoordinateConversionError> {
let result = robust_insphere_positive_oriented(simplex_points, test_point)?;
Ok(insphere_to_i32(result))
}
}
#[derive(Clone, Default, Debug)]
pub struct AdaptiveKernel<T> {
_phantom: PhantomData<T>,
}
impl<T> AdaptiveKernel<T> {
#[must_use]
pub const fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<T, const D: usize> Kernel<D> for AdaptiveKernel<T>
where
T: CoordinateScalar,
{
type Scalar = T;
fn orientation(
&self,
points: &[Point<Self::Scalar, D>],
) -> Result<i32, CoordinateConversionError> {
if points.len() != D + 1 {
return Err(CoordinateConversionError::InvalidSimplexPointCount {
actual: points.len(),
expected: D + 1,
dimension: D,
});
}
let exact = robust_orientation(points)?;
match exact {
Orientation::POSITIVE => return Ok(1),
Orientation::NEGATIVE => return Ok(-1),
Orientation::DEGENERATE => {}
}
let mut f64_points: SmallBuffer<Point<f64, D>, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(points.len());
for point in points {
f64_points.push(Point::new(safe_coords_to_f64(point.coords())?));
}
sos_orientation_sign(&f64_points).map_or(Ok(0), Ok)
}
fn in_sphere(
&self,
simplex_points: &[Point<Self::Scalar, D>],
test_point: &Point<Self::Scalar, D>,
) -> Result<i32, CoordinateConversionError> {
if simplex_points.len() != D + 1 {
return Err(CoordinateConversionError::InvalidSimplexPointCount {
actual: simplex_points.len(),
expected: D + 1,
dimension: D,
});
}
let signs = relative_insphere_signs(simplex_points, test_point)?;
let rel_orient_sign = signs.relative_orientation;
let insphere_det_sign = signs.insphere_determinant;
if rel_orient_sign != 0 && insphere_det_sign != 0 {
return Ok(relative_insphere_effective_sign(signs));
}
let mut f64_simplex: SmallBuffer<Point<f64, D>, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(simplex_points.len());
for point in simplex_points {
f64_simplex.push(Point::new(safe_coords_to_f64(point.coords())?));
}
let f64_test = Point::new(safe_coords_to_f64(test_point.coords())?);
let orient_factor: i32 = if rel_orient_sign != 0 {
-rel_orient_sign
} else {
let sos_abs = sos_orientation_sign(&f64_simplex)?;
if D.is_multiple_of(2) {
-sos_abs
} else {
sos_abs
}
};
let insphere_effective: i32 = if insphere_det_sign != 0 {
insphere_det_sign
} else {
sos_insphere_sign(&f64_simplex, &f64_test)?
};
Ok((insphere_effective * orient_factor).signum())
}
fn in_sphere_positive_oriented(
&self,
simplex_points: &[Point<Self::Scalar, D>],
test_point: &Point<Self::Scalar, D>,
) -> Result<i32, CoordinateConversionError> {
if simplex_points.len() != D + 1 {
return Err(CoordinateConversionError::InvalidSimplexPointCount {
actual: simplex_points.len(),
expected: D + 1,
dimension: D,
});
}
let determinant_sign = relative_insphere_determinant_sign(simplex_points, test_point)?;
let orient_factor = if D.is_multiple_of(2) { -1 } else { 1 };
let insphere_effective = if determinant_sign != 0 {
determinant_sign
} else {
let mut f64_simplex: SmallBuffer<Point<f64, D>, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(simplex_points.len());
for point in simplex_points {
f64_simplex.push(Point::new(safe_coords_to_f64(point.coords())?));
}
let f64_test = Point::new(safe_coords_to_f64(test_point.coords())?);
sos_insphere_sign(&f64_simplex, &f64_test)?
};
Ok((insphere_effective * orient_factor).signum())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::point::Point;
use crate::geometry::traits::coordinate::Coordinate;
fn standard_simplex<const D: usize>() -> Vec<Point<f64, D>> {
let mut points = Vec::with_capacity(D + 1);
points.push(Point::new([0.0; D]));
for i in 0..D {
let mut coords = [0.0; D];
coords[i] = 1.0;
points.push(Point::new(coords));
}
points
}
fn degenerate_simplex<const D: usize>() -> Vec<Point<f64, D>> {
let mut points = Vec::with_capacity(D + 1);
points.push(Point::new([0.0; D]));
for i in 0..D.saturating_sub(1) {
let mut coords = [0.0; D];
coords[i] = 1.0;
points.push(Point::new(coords));
}
let mut bary = [0.0; D];
for c in bary.iter_mut().take(D.saturating_sub(1)) {
*c = 0.5;
}
points.push(Point::new(bary));
points
}
fn inside_point<const D: usize>() -> Point<f64, D> {
Point::new([0.1; D])
}
fn outside_point<const D: usize>() -> Point<f64, D> {
Point::new([2.0; D])
}
fn cospherical_test<const D: usize>() -> Point<f64, D> {
Point::new([1.0; D])
}
fn off_plane_test<const D: usize>() -> Point<f64, D> {
let mut coords = [0.0; D];
coords[D - 1] = 1.0;
Point::new(coords)
}
fn coplanar_far_test<const D: usize>() -> Point<f64, D> {
let mut coords = [0.0; D];
coords[0] = 3.0;
Point::new(coords)
}
macro_rules! gen_standard_kernel_tests {
($dim:literal, $name:ident, $kernel:expr) => {
pastey::paste! {
#[test]
fn [<test_ $name _orientation_ $dim d_nondegenerate>]() {
let kernel = $kernel;
let simplex = standard_simplex::<$dim>();
let result = kernel.orientation(&simplex).unwrap();
assert!(
result == 1 || result == -1,
"expected ±1, got {result}"
);
}
#[test]
fn [<test_ $name _orientation_ $dim d_degenerate>]() {
let kernel = $kernel;
let simplex = degenerate_simplex::<$dim>();
let result = kernel.orientation(&simplex).unwrap();
assert_eq!(result, 0, "degenerate simplex must give 0");
}
#[test]
fn [<test_ $name _insphere_ $dim d_inside>]() {
let kernel = $kernel;
let simplex = standard_simplex::<$dim>();
let test = inside_point::<$dim>();
let result = kernel.in_sphere(&simplex, &test).unwrap();
assert_eq!(result, 1, "point should be INSIDE");
}
#[test]
fn [<test_ $name _insphere_ $dim d_outside>]() {
let kernel = $kernel;
let simplex = standard_simplex::<$dim>();
let test = outside_point::<$dim>();
let result = kernel.in_sphere(&simplex, &test).unwrap();
assert_eq!(result, -1, "point should be OUTSIDE");
}
}
};
}
gen_standard_kernel_tests!(2, fast, FastKernel::<f64>::new());
gen_standard_kernel_tests!(3, fast, FastKernel::<f64>::new());
gen_standard_kernel_tests!(4, fast, FastKernel::<f64>::new());
gen_standard_kernel_tests!(5, fast, FastKernel::<f64>::new());
gen_standard_kernel_tests!(2, robust, RobustKernel::<f64>::new());
gen_standard_kernel_tests!(3, robust, RobustKernel::<f64>::new());
gen_standard_kernel_tests!(4, robust, RobustKernel::<f64>::new());
gen_standard_kernel_tests!(5, robust, RobustKernel::<f64>::new());
#[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_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_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_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_eq!(
result,
Err(CoordinateConversionError::InvalidSimplexPointCount {
actual: 2,
expected: 4,
dimension: 3,
})
);
}
#[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_eq!(
result,
Err(CoordinateConversionError::DegenerateSimplex {
dimension: 3,
reason: DegenerateSimplexReason::ZeroOrientation,
})
);
}
#[test]
fn test_robust_kernel_positive_oriented_insphere_boundary_maps_to_zero() {
let kernel = RobustKernel::<f64>::new();
let simplex = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
];
assert_eq!(kernel.orientation(&simplex).unwrap(), 1);
assert_eq!(
kernel
.in_sphere_positive_oriented(&simplex, &Point::new([1.0, 1.0]))
.unwrap(),
0
);
}
#[test]
fn test_robust_kernel_positive_oriented_insphere_wrong_arity_errors() {
let kernel = RobustKernel::<f64>::new();
let simplex: [Point<f64, 2>; 2] = [Point::new([0.0, 0.0]), Point::new([1.0, 0.0])];
let err = kernel
.in_sphere_positive_oriented(&simplex, &Point::new([0.25, 0.25]))
.unwrap_err();
assert_eq!(
err,
CoordinateConversionError::InvalidSimplexPointCount {
actual: 2,
expected: 3,
dimension: 2,
}
);
}
macro_rules! gen_adaptive_kernel_tests {
($dim:literal) => {
pastey::paste! {
#[test]
fn [<test_adaptive_orientation_ $dim d_agrees>]() {
let adaptive = AdaptiveKernel::<f64>::new();
let fast = FastKernel::<f64>::new();
let simplex = standard_simplex::<$dim>();
assert_eq!(
adaptive.orientation(&simplex).unwrap(),
fast.orientation(&simplex).unwrap(),
"AdaptiveKernel must agree with FastKernel on non-degenerate"
);
}
#[test]
fn [<test_adaptive_orientation_ $dim d_degenerate_sos_nonzero>]() {
let kernel = AdaptiveKernel::<f64>::new();
let simplex = degenerate_simplex::<$dim>();
let result = kernel.orientation(&simplex).unwrap();
assert!(
result == 1 || result == -1,
"AdaptiveKernel SoS orientation must never return 0, got {result}"
);
}
#[test]
fn [<test_adaptive_orientation_ $dim d_degenerate_deterministic>]() {
let kernel = AdaptiveKernel::<f64>::new();
let simplex = degenerate_simplex::<$dim>();
let results: Vec<i32> = (0..10)
.map(|_| kernel.orientation(&simplex).unwrap())
.collect();
assert!(
results.iter().all(|&r| r == results[0]),
"Degenerate SoS orientation must be deterministic"
);
}
#[test]
fn [<test_adaptive_insphere_ $dim d_agrees>]() {
let adaptive = AdaptiveKernel::<f64>::new();
let fast = FastKernel::<f64>::new();
let simplex = standard_simplex::<$dim>();
let inside = inside_point::<$dim>();
assert_eq!(
adaptive.in_sphere(&simplex, &inside).unwrap(),
fast.in_sphere(&simplex, &inside).unwrap(),
"Must agree on clearly inside point"
);
let outside = outside_point::<$dim>();
assert_eq!(
adaptive.in_sphere(&simplex, &outside).unwrap(),
fast.in_sphere(&simplex, &outside).unwrap(),
"Must agree on clearly outside point"
);
}
#[test]
fn [<test_adaptive_positive_oriented_insphere_ $dim d_agrees>]() {
let kernel = AdaptiveKernel::<f64>::new();
let mut simplex = standard_simplex::<$dim>();
if kernel.orientation(&simplex).unwrap() < 0 {
simplex.swap(0, 1);
}
assert_eq!(kernel.orientation(&simplex).unwrap(), 1);
for test in [
inside_point::<$dim>(),
outside_point::<$dim>(),
cospherical_test::<$dim>(),
] {
assert_eq!(
kernel.in_sphere_positive_oriented(&simplex, &test).unwrap(),
kernel.in_sphere(&simplex, &test).unwrap(),
"positive-oriented fast path must preserve AdaptiveKernel semantics"
);
}
}
#[test]
fn [<test_adaptive_insphere_ $dim d_cospherical_nonzero>]() {
let kernel = AdaptiveKernel::<f64>::new();
let simplex = standard_simplex::<$dim>();
let test = cospherical_test::<$dim>();
let result = kernel.in_sphere(&simplex, &test).unwrap();
assert!(
result == 1 || result == -1,
"AdaptiveKernel insphere must never return 0, got {result}"
);
}
#[test]
fn [<test_adaptive_insphere_ $dim d_cospherical_deterministic>]() {
let kernel = AdaptiveKernel::<f64>::new();
let simplex = standard_simplex::<$dim>();
let test = cospherical_test::<$dim>();
let results: Vec<i32> = (0..10)
.map(|_| kernel.in_sphere(&simplex, &test).unwrap())
.collect();
assert!(
results.iter().all(|&r| r == results[0]),
"Degenerate insphere must be deterministic"
);
}
#[test]
fn [<test_adaptive_insphere_ $dim d_orient_degenerate>]() {
let kernel = AdaptiveKernel::<f64>::new();
let simplex = degenerate_simplex::<$dim>();
let test = off_plane_test::<$dim>();
let result = kernel.in_sphere(&simplex, &test).unwrap();
assert!(
result == 1 || result == -1,
"Must resolve orient-degenerate insphere, got {result}"
);
}
#[test]
fn [<test_adaptive_insphere_ $dim d_both_degenerate>]() {
let kernel = AdaptiveKernel::<f64>::new();
let simplex = degenerate_simplex::<$dim>();
let test = coplanar_far_test::<$dim>();
let result = kernel.in_sphere(&simplex, &test).unwrap();
assert!(
result == 1 || result == -1,
"Must resolve both-degenerate insphere, got {result}"
);
}
}
};
}
gen_adaptive_kernel_tests!(2);
gen_adaptive_kernel_tests!(3);
gen_adaptive_kernel_tests!(4);
gen_adaptive_kernel_tests!(5);
macro_rules! gen_kernel_consistency_tests {
($dim:literal) => {
pastey::paste! {
#[test]
fn [<test_kernel_consistency_ $dim d_orientation>]() {
let fast = FastKernel::<f64>::new();
let robust = RobustKernel::<f64>::new();
let adaptive = AdaptiveKernel::<f64>::new();
let simplex = standard_simplex::<$dim>();
let f = fast.orientation(&simplex).unwrap();
let r = robust.orientation(&simplex).unwrap();
let a = adaptive.orientation(&simplex).unwrap();
assert_eq!(f, r, "Fast vs Robust");
assert_eq!(f, a, "Fast vs Adaptive");
}
#[test]
fn [<test_kernel_consistency_ $dim d_insphere>]() {
let fast = FastKernel::<f64>::new();
let robust = RobustKernel::<f64>::new();
let adaptive = AdaptiveKernel::<f64>::new();
let simplex = standard_simplex::<$dim>();
let test = inside_point::<$dim>();
let f = fast.in_sphere(&simplex, &test).unwrap();
let r = robust.in_sphere(&simplex, &test).unwrap();
let a = adaptive.in_sphere(&simplex, &test).unwrap();
assert_eq!(f, r, "Fast vs Robust");
assert_eq!(f, a, "Fast vs Adaptive");
}
}
};
}
gen_kernel_consistency_tests!(2);
gen_kernel_consistency_tests!(3);
gen_kernel_consistency_tests!(4);
gen_kernel_consistency_tests!(5);
#[test]
fn test_adaptive_kernel_default_trait() {
let _adaptive: AdaptiveKernel<f64> = AdaptiveKernel::default();
}
#[test]
fn test_adaptive_kernel_wrong_point_count() {
let kernel = AdaptiveKernel::<f64>::new();
let points = [Point::new([0.0, 0.0]), Point::new([1.0, 0.0])];
assert!(kernel.orientation(&points).is_err());
let simplex = [Point::new([0.0, 0.0]), Point::new([1.0, 0.0])];
let test = Point::new([0.5, 0.5]);
assert!(kernel.in_sphere(&simplex, &test).is_err());
}
macro_rules! gen_sos_identical_points_test {
($dim:literal) => {
pastey::paste! {
#[test]
fn [<test_adaptive_sos_identical_points_ $dim d>]() {
let kernel = AdaptiveKernel::<f64>::new();
let points: Vec<Point<f64, $dim>> =
vec![Point::new([0.42; $dim]); $dim + 1];
let result = kernel.orientation(&points).unwrap();
assert_eq!(
result, 0,
"{}D: identical points must yield orientation 0, got {result}",
$dim
);
}
}
};
}
gen_sos_identical_points_test!(2);
gen_sos_identical_points_test!(3);
gen_sos_identical_points_test!(4);
gen_sos_identical_points_test!(5);
const fn assert_exact_predicates<T, const D: usize>()
where
T: ExactPredicates<D>,
{
}
#[test]
fn test_adaptive_kernel_implements_exact_predicates() {
assert_exact_predicates::<AdaptiveKernel<f64>, 2>();
assert_exact_predicates::<AdaptiveKernel<f64>, 5>();
assert_exact_predicates::<AdaptiveKernel<f32>, 2>();
assert_exact_predicates::<AdaptiveKernel<f32>, 5>();
}
#[test]
fn test_robust_kernel_implements_exact_predicates() {
assert_exact_predicates::<RobustKernel<f64>, 2>();
assert_exact_predicates::<RobustKernel<f64>, 5>();
assert_exact_predicates::<RobustKernel<f32>, 2>();
assert_exact_predicates::<RobustKernel<f32>, 5>();
}
#[test]
fn test_fast_kernel_does_not_implement_exact_predicates() {
fn _requires_exact<T: ExactPredicates<3>>() {}
}
}