#![forbid(unsafe_code)]
use crate::geometry::matrix::{LaError, MatrixError, StackMatrixDispatchError};
use ordered_float::OrderedFloat;
use serde::{Serialize, de::DeserializeOwned};
use std::{
fmt::{self, Debug, Display},
hash::{Hash, Hasher},
};
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum InvalidCoordinateValue {
Nan,
PositiveInfinity,
NegativeInfinity,
Other(String),
}
impl InvalidCoordinateValue {
pub(crate) fn from_debug<T: Debug>(value: &T) -> Self {
let value = format!("{value:?}");
match value.as_str() {
"NaN" => Self::Nan,
"inf" => Self::PositiveInfinity,
"-inf" => Self::NegativeInfinity,
_ => Self::Other(value),
}
}
}
impl fmt::Display for InvalidCoordinateValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Nan => f.write_str("NaN"),
Self::PositiveInfinity => f.write_str("inf"),
Self::NegativeInfinity => f.write_str("-inf"),
Self::Other(value) => f.write_str(value),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
#[must_use]
pub struct FiniteCoordinateValue(f64);
impl FiniteCoordinateValue {
pub fn try_new(value: f64) -> Result<Self, InvalidCoordinateValue> {
if value.is_finite() {
Ok(Self(value))
} else {
Err(InvalidCoordinateValue::from_debug(&value))
}
}
#[must_use]
pub const fn get(self) -> f64 {
self.0
}
}
impl TryFrom<f64> for FiniteCoordinateValue {
type Error = InvalidCoordinateValue;
fn try_from(value: f64) -> Result<Self, Self::Error> {
Self::try_new(value)
}
}
impl fmt::Display for FiniteCoordinateValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DegenerateSimplexReason {
ZeroOrientation,
VanishingSosCofactors,
}
impl fmt::Display for DegenerateSimplexReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ZeroOrientation => f.write_str("zero orientation"),
Self::VanishingSosCofactors => f.write_str("vanishing SoS cofactors"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum CoordinateConversionValue {
Scalar(FiniteCoordinateValue),
UnsignedInteger(usize),
NonFinite(InvalidCoordinateValue),
Other(String),
}
impl CoordinateConversionValue {
#[must_use]
pub fn from_f64(value: f64) -> Self {
FiniteCoordinateValue::try_new(value).map_or_else(Self::NonFinite, Self::Scalar)
}
#[must_use]
pub const fn from_usize(value: usize) -> Self {
Self::UnsignedInteger(value)
}
pub(crate) fn from_numeric_debug<T>(value: &T) -> Self
where
T: Debug + num_traits::ToPrimitive,
{
value
.to_f64()
.map_or_else(|| Self::Other(format!("{value:?}")), Self::from_f64)
}
}
impl Display for CoordinateConversionValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Scalar(value) => write!(f, "{value}"),
Self::UnsignedInteger(value) => write!(f, "{value}"),
Self::NonFinite(value) => write!(f, "{value}"),
Self::Other(value) => f.write_str(value),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[must_use]
pub struct CoordinateValues(Vec<CoordinateConversionValue>);
impl CoordinateValues {
pub(crate) fn from_numeric_slice<T>(values: &[T]) -> Self
where
T: Debug + num_traits::ToPrimitive,
{
Self(
values
.iter()
.map(CoordinateConversionValue::from_numeric_debug)
.collect(),
)
}
#[must_use]
pub fn as_slice(&self) -> &[CoordinateConversionValue] {
&self.0
}
#[must_use]
pub fn into_vec(self) -> Vec<CoordinateConversionValue> {
self.0
}
}
impl<const D: usize> From<[f64; D]> for CoordinateValues {
fn from(values: [f64; D]) -> Self {
Self(
values
.into_iter()
.map(CoordinateConversionValue::from_f64)
.collect(),
)
}
}
impl fmt::Display for CoordinateValues {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("[")?;
for (idx, value) in self.0.iter().enumerate() {
if idx != 0 {
f.write_str(", ")?;
}
write!(f, "{value}")?;
}
f.write_str("]")
}
}
#[derive(Clone, Debug, thiserror::Error, PartialEq)]
#[non_exhaustive]
pub enum CoordinateConversionError {
#[error(
"Failed to convert coordinate at index {coordinate_index} from {from_type} to {to_type}: {coordinate_value}"
)]
ConversionFailed {
coordinate_index: usize,
coordinate_value: CoordinateConversionValue,
from_type: &'static str,
to_type: &'static str,
},
#[error(
"Non-finite value (NaN or infinity) at coordinate index {coordinate_index}: {coordinate_value}"
)]
NonFiniteValue {
coordinate_index: usize,
coordinate_value: InvalidCoordinateValue,
},
#[error(
"Invalid simplex point count for dimension {dimension}: expected {expected}, got {actual}"
)]
InvalidSimplexPointCount {
actual: usize,
expected: usize,
dimension: usize,
},
#[error("Degenerate simplex in dimension {dimension}: {reason}")]
DegenerateSimplex {
dimension: usize,
reason: DegenerateSimplexReason,
},
#[error("Insphere consistency check failed: {details}")]
InsphereInconsistency {
simplex_points: String,
test_point: String,
details: String,
},
#[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,
},
}
impl From<StackMatrixDispatchError> for CoordinateConversionError {
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 CoordinateConversionError {
fn from(source: LaError) -> Self {
Self::from(StackMatrixDispatchError::from(source))
}
}
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum CoordinateValidationError {
#[error(
"Invalid coordinate at index {coordinate_index} in dimension {dimension}: {coordinate_value}"
)]
InvalidCoordinate {
coordinate_index: usize,
coordinate_value: InvalidCoordinateValue,
dimension: usize,
},
}
pub const DEFAULT_TOLERANCE_F64: f64 = 1e-15;
pub trait FiniteCheck {
fn is_finite_generic(&self) -> bool;
}
macro_rules! impl_finite_check {
(float: $($t:ty),*) => {
$(
impl FiniteCheck for $t {
#[inline(always)]
fn is_finite_generic(&self) -> bool {
self.is_finite()
}
}
)*
};
}
impl_finite_check!(float: f64);
pub trait OrderedEq {
fn ordered_eq(&self, other: &Self) -> bool;
}
macro_rules! impl_ordered_eq {
(float: $($t:ty),*) => {
$(
impl OrderedEq for $t {
#[inline(always)]
fn ordered_eq(&self, other: &Self) -> bool {
OrderedFloat(*self) == OrderedFloat(*other)
}
}
)*
};
}
impl_ordered_eq!(float: f64);
pub trait OrderedCmp {
fn ordered_partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering>;
}
macro_rules! impl_ordered_cmp {
(float: $($t:ty),*) => {
$(
impl OrderedCmp for $t {
#[inline(always)]
fn ordered_partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(OrderedFloat(*self).cmp(&OrderedFloat(*other)))
}
}
)*
};
}
impl_ordered_cmp!(float: f64);
pub trait HashCoordinate {
fn hash_scalar<H: Hasher>(&self, state: &mut H);
}
macro_rules! impl_hash_coordinate {
(float: $($t:ty),*) => {
$(
impl HashCoordinate for $t {
#[inline(always)]
fn hash_scalar<H: Hasher>(&self, state: &mut H) {
OrderedFloat(*self).hash(state);
}
}
)*
};
}
impl_hash_coordinate!(float: f64);
pub const F64_MANTISSA_DIGITS: u32 = f64::MANTISSA_DIGITS;
pub trait CoordinateRepresentation: Copy + Default + Debug + Serialize + DeserializeOwned {}
impl<T> CoordinateRepresentation for T where T: Copy + Default + Debug + Serialize + DeserializeOwned
{}
pub trait CoordinateIdentity: Eq + Hash + PartialOrd {}
impl<T> CoordinateIdentity for T where T: Eq + Hash + PartialOrd {}
pub trait Coordinate<const D: usize>: Sized {
#[must_use]
fn dim(&self) -> usize {
D
}
fn try_new(coords: [f64; D]) -> Result<Self, CoordinateValidationError>;
#[must_use]
fn to_array(&self) -> [f64; D];
#[must_use]
fn get(&self, index: usize) -> Option<f64>;
#[must_use]
fn origin() -> Self {
Self::try_new([0.0; D]).expect("zero coordinates satisfy Coordinate::origin")
}
fn validate(&self) -> Result<(), CoordinateValidationError>;
fn hash_coordinate<H: Hasher>(&self, state: &mut H);
#[must_use]
fn ordered_equals(&self, other: &Self) -> bool;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::point::Point;
use approx::assert_relative_eq;
use std::assert_matches;
use std::collections::{HashSet, hash_map::DefaultHasher};
use std::error::Error;
use std::hash::Hasher;
#[test]
fn coordinate_conversion_error_preserves_matrix_error_sources() {
let matrix_error = MatrixError::OutOfBounds {
row: 2,
column: 1,
dimension: 2,
};
let converted = CoordinateConversionError::from(matrix_error.clone());
assert_eq!(
converted,
CoordinateConversionError::MatrixError {
source: matrix_error
}
);
assert!(converted.source().is_some());
}
#[test]
fn stack_matrix_dispatch_errors_map_to_coordinate_conversion_errors() {
let unsupported =
CoordinateConversionError::from(StackMatrixDispatchError::UnsupportedDim {
k: 8,
max: 7,
});
assert_eq!(
unsupported,
CoordinateConversionError::UnsupportedMatrixDimension {
requested: 8,
max: 7,
}
);
let mismatch = CoordinateConversionError::from(
StackMatrixDispatchError::ActiveBlockDimensionMismatch { k: 4, dim: 3 },
);
assert_eq!(
mismatch,
CoordinateConversionError::MatrixDimensionMismatch {
active: 4,
matrix_dimension: 3,
}
);
let matrix_error = MatrixError::OutOfBounds {
row: 3,
column: 1,
dimension: 3,
};
let converted = CoordinateConversionError::from(StackMatrixDispatchError::Matrix {
source: matrix_error.clone(),
});
assert_eq!(
converted,
CoordinateConversionError::MatrixError {
source: matrix_error
}
);
}
#[test]
fn coordinate_conversion_error_clones_linear_algebra_source() {
let source = LaError::non_finite_input_matrix(1, 2);
let converted = CoordinateConversionError::LinearAlgebraFailure { source };
assert_eq!(converted.clone(), converted);
assert!(converted.source().is_some());
}
#[test]
fn la_errors_map_to_public_coordinate_conversion_errors() {
let unsupported = CoordinateConversionError::from(LaError::unsupported_dimension(9, 7));
assert_eq!(
unsupported,
CoordinateConversionError::UnsupportedMatrixDimension {
requested: 9,
max: 7,
}
);
let index_error = CoordinateConversionError::from(LaError::index_out_of_bounds(3, 4, 2));
assert_eq!(
index_error,
CoordinateConversionError::MatrixError {
source: MatrixError::OutOfBounds {
row: 3,
column: 4,
dimension: 2,
},
}
);
}
#[test]
fn degenerate_simplex_reason_display_covers_all_variants() {
assert_eq!(
DegenerateSimplexReason::ZeroOrientation.to_string(),
"zero orientation"
);
assert_eq!(
DegenerateSimplexReason::VanishingSosCofactors.to_string(),
"vanishing SoS cofactors"
);
}
#[test]
fn finite_coordinate_value_rejects_non_finite_input() {
assert_relative_eq!(
FiniteCoordinateValue::try_new(1.25).unwrap().get(),
1.25,
epsilon = f64::EPSILON
);
assert_eq!(
FiniteCoordinateValue::try_new(f64::NAN),
Err(InvalidCoordinateValue::Nan)
);
assert_eq!(
FiniteCoordinateValue::try_new(f64::INFINITY),
Err(InvalidCoordinateValue::PositiveInfinity)
);
assert_eq!(
FiniteCoordinateValue::try_new(f64::NEG_INFINITY),
Err(InvalidCoordinateValue::NegativeInfinity)
);
}
#[test]
fn finite_coordinate_value_try_from_parses_at_raw_boundary() {
assert_relative_eq!(
FiniteCoordinateValue::try_from(-3.5).unwrap().get(),
-3.5,
epsilon = f64::EPSILON
);
assert_eq!(
FiniteCoordinateValue::try_from(f64::NEG_INFINITY),
Err(InvalidCoordinateValue::NegativeInfinity)
);
}
#[test]
fn invalid_coordinate_value_display_preserves_custom_payload() {
assert_eq!(
InvalidCoordinateValue::Other("not finite".to_owned()).to_string(),
"not finite"
);
}
#[test]
fn coordinate_conversion_value_parses_raw_f64_at_boundary() {
assert_matches!(
CoordinateConversionValue::from_f64(2.5),
CoordinateConversionValue::Scalar(value)
if (value.get() - 2.5).abs() < f64::EPSILON
);
assert_matches!(
CoordinateConversionValue::from_f64(f64::NAN),
CoordinateConversionValue::NonFinite(InvalidCoordinateValue::Nan)
);
assert_matches!(
CoordinateConversionValue::from_f64(f64::INFINITY),
CoordinateConversionValue::NonFinite(InvalidCoordinateValue::PositiveInfinity)
);
}
#[test]
fn coordinate_values_preserve_typed_payloads() {
let coordinates = CoordinateValues::from([2.5, f64::NEG_INFINITY]);
assert_matches!(
coordinates.as_slice(),
[
CoordinateConversionValue::Scalar(value),
CoordinateConversionValue::NonFinite(InvalidCoordinateValue::NegativeInfinity)
] if (value.get() - 2.5).abs() < f64::EPSILON
);
assert_eq!(coordinates.to_string(), "[2.5, -inf]");
assert_eq!(
CoordinateConversionValue::from_usize(3),
CoordinateConversionValue::UnsignedInteger(3)
);
assert_eq!(
coordinates.into_vec(),
vec![
CoordinateConversionValue::from_f64(2.5),
CoordinateConversionValue::NonFinite(InvalidCoordinateValue::NegativeInfinity),
]
);
}
#[test]
fn coordinate_trait_basic_functionality() {
let coord: Point<3> = Point::try_new([1.0, 2.0, 3.0]).expect("finite point coordinates");
assert_eq!(coord.dim(), 3);
assert_relative_eq!(
coord.to_array().as_slice(),
[1.0, 2.0, 3.0].as_slice(),
epsilon = DEFAULT_TOLERANCE_F64
);
assert_relative_eq!(coord.get(0).unwrap(), 1.0, epsilon = DEFAULT_TOLERANCE_F64);
assert_relative_eq!(coord.get(1).unwrap(), 2.0, epsilon = DEFAULT_TOLERANCE_F64);
assert_relative_eq!(coord.get(2).unwrap(), 3.0, epsilon = DEFAULT_TOLERANCE_F64);
assert_eq!(coord.get(3), None);
assert_eq!(coord.get(10), None);
let coord_single: Point<1> = Point::try_new([42.0]).expect("finite point coordinates");
assert_eq!(coord_single.dim(), 1);
assert_relative_eq!(
coord_single.get(0).unwrap(),
42.0,
epsilon = DEFAULT_TOLERANCE_F64
);
assert_eq!(coord_single.get(1), None);
let coord_zero: Point<0> = Point::try_new([]).expect("finite point coordinates");
assert_eq!(coord_zero.dim(), 0);
assert_eq!(coord_zero.to_array().len(), 0);
assert_eq!(coord_zero.get(0), None);
assert!(coord_zero.validate().is_ok());
let coord_large: Point<10> =
Point::try_new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0])
.expect("finite point coordinates");
assert_eq!(coord_large.dim(), 10);
assert_eq!(coord_large.get(10), None);
assert!(coord_large.validate().is_ok());
}
#[test]
fn coordinate_trait_new() {
let coord1: Point<2> = Point::try_new([5.0, 6.0]).expect("finite point coordinates");
assert_relative_eq!(
coord1.to_array().as_slice(),
[5.0, 6.0].as_slice(),
epsilon = DEFAULT_TOLERANCE_F64
);
let coord2: Point<2> = Point::try_new([5.0, 6.0]).expect("finite point coordinates");
assert_relative_eq!(
coord2.to_array().as_slice(),
[5.0, 6.0].as_slice(),
epsilon = DEFAULT_TOLERANCE_F64
);
assert_eq!(coord1, coord2);
}
#[test]
fn coordinate_trait_origin() {
let origin_single: Point<1> = Point::origin();
assert_relative_eq!(
origin_single.to_array().as_slice(),
[0.0].as_slice(),
epsilon = DEFAULT_TOLERANCE_F64
);
let origin_triple: Point<3> = Point::origin();
assert_relative_eq!(
origin_triple.to_array().as_slice(),
[0.0, 0.0, 0.0].as_slice(),
epsilon = DEFAULT_TOLERANCE_F64
);
let origin_zero: Point<0> = Point::origin();
assert_eq!(origin_zero.to_array().len(), 0);
let origin_large: Point<10> = Point::origin();
assert_relative_eq!(
origin_large.to_array().as_slice(),
[0.0; 10].as_slice(),
epsilon = DEFAULT_TOLERANCE_F64
);
}
#[test]
fn coordinate_trait_validate_comprehensive() {
let valid_cases = [
([1.0, 2.0, 3.0], "positive values"),
([-1.0, -2.0, -3.0], "negative values"),
([0.0, 0.0, 0.0], "zeros"),
([1e10, 2e10, 3e10], "large values"),
([1e-10, 2e-10, 3e-10], "small values"),
];
for &(coords, description) in &valid_cases {
let coord: Point<3> = Point::try_new(coords).expect("finite point coordinates");
assert!(coord.validate().is_ok(), "Valid case failed: {description}");
}
let invalid_cases = [
([f64::NAN, 2.0, 3.0], 0, 3, "NaN at start"),
([1.0, f64::NAN, 3.0], 1, 3, "NaN in middle"),
([1.0, 2.0, f64::NAN], 2, 3, "NaN at end"),
([f64::INFINITY, 2.0, 3.0], 0, 3, "positive infinity"),
([1.0, f64::NEG_INFINITY, 3.0], 1, 3, "negative infinity"),
];
for &(coords, expected_index, expected_dim, description) in &invalid_cases {
let result = Point::<3>::try_new(coords);
assert!(result.is_err(), "Invalid case should fail: {description}");
if let Err(CoordinateValidationError::InvalidCoordinate {
coordinate_index,
dimension,
..
}) = result
{
assert_eq!(
coordinate_index, expected_index,
"Wrong index for: {description}"
);
assert_eq!(
dimension, expected_dim,
"Wrong dimension for: {description}"
);
}
}
let multi_invalid = Point::<4>::try_new([f64::NAN, f64::INFINITY, f64::NAN, 1.0]);
if let Err(CoordinateValidationError::InvalidCoordinate {
coordinate_index,
dimension,
..
}) = multi_invalid
{
assert_eq!(
coordinate_index, 0,
"Should report first invalid coordinate"
);
assert_eq!(dimension, 4);
}
let invalid_1d = Point::<1>::try_new([f64::NAN]);
if let Err(CoordinateValidationError::InvalidCoordinate { dimension, .. }) = invalid_1d {
assert_eq!(dimension, 1);
}
let invalid_5d = Point::<5>::try_new([1.0, 2.0, f64::INFINITY, 4.0, 5.0]);
if let Err(CoordinateValidationError::InvalidCoordinate {
coordinate_index,
dimension,
..
}) = invalid_5d
{
assert_eq!(coordinate_index, 2);
assert_eq!(dimension, 5);
}
}
#[test]
fn coordinate_trait_hash_coordinate_comprehensive() {
let coord1: Point<3> = Point::try_new([1.0, 2.0, 3.0]).expect("finite point coordinates");
let coord2: Point<3> = Point::try_new([1.0, 2.0, 3.0]).expect("finite point coordinates");
let coord3: Point<3> = Point::try_new([1.0, 2.0, 4.0]).expect("finite point coordinates");
let mut hasher1 = DefaultHasher::new();
let mut hasher2 = DefaultHasher::new();
let mut hasher3 = DefaultHasher::new();
coord1.hash_coordinate(&mut hasher1);
coord2.hash_coordinate(&mut hasher2);
coord3.hash_coordinate(&mut hasher3);
assert_eq!(
hasher1.finish(),
hasher2.finish(),
"Same coordinates should have same hash"
);
assert_ne!(
hasher1.finish(),
hasher3.finish(),
"Different coordinates should have different hash"
);
assert!(Point::<2>::try_new([f64::NAN, 1.0]).is_err());
assert!(Point::<2>::try_new([f64::INFINITY, 1.0]).is_err());
}
#[test]
fn coordinate_trait_ordered_equals_comprehensive() {
let coord1: Point<3> = Point::try_new([1.0, 2.0, 3.0]).expect("finite point coordinates");
let coord2: Point<3> = Point::try_new([1.0, 2.0, 3.0]).expect("finite point coordinates");
let coord3: Point<3> = Point::try_new([1.0, 2.0, 4.0]).expect("finite point coordinates");
assert!(coord1.ordered_equals(&coord2));
assert!(coord2.ordered_equals(&coord1));
assert!(!coord1.ordered_equals(&coord3));
assert!(Point::<3>::try_new([f64::NAN, 2.0, 3.0]).is_err());
assert!(Point::<2>::try_new([f64::INFINITY, 2.0]).is_err());
assert!(Point::<4>::try_new([f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 1.0]).is_err());
}
#[test]
fn coordinate_validation_error_properties() {
let error = CoordinateValidationError::InvalidCoordinate {
coordinate_index: 1,
coordinate_value: InvalidCoordinateValue::Nan,
dimension: 3,
};
let debug_str = format!("{error:?}");
assert!(debug_str.contains("InvalidCoordinate"));
assert!(debug_str.contains("coordinate_index: 1"));
assert!(debug_str.contains("dimension: 3"));
let display_str = format!("{error}");
assert!(display_str.contains("Invalid coordinate at index 1 in dimension 3: NaN"));
let error_clone = error.clone();
assert_eq!(error, error_clone);
let different_error = CoordinateValidationError::InvalidCoordinate {
coordinate_index: 2,
coordinate_value: InvalidCoordinateValue::PositiveInfinity,
dimension: 3,
};
assert_ne!(error, different_error);
}
#[test]
fn coordinate_default_tolerance_constant() {
assert_relative_eq!(DEFAULT_TOLERANCE_F64, 1e-15_f64, epsilon = f64::EPSILON);
let a_f64 = 1.0f64;
let b_f64 = 1.0f64 + DEFAULT_TOLERANCE_F64 / 2.0;
assert!((a_f64 - b_f64).abs() < DEFAULT_TOLERANCE_F64);
}
#[test]
fn coordinate_trait_hash_collision_resistance() {
let mut hashes = HashSet::new();
let test_coords = [
[1.0, 2.0, 3.0],
[1.1, 2.0, 3.0],
[1.0, 2.1, 3.0],
[1.0, 2.0, 3.1],
[0.0, 0.0, 0.0],
[-1.0, -2.0, -3.0],
[1e10, 1e-10, 0.0],
];
for coords in test_coords {
let coord: Point<3> = Point::try_new(coords).expect("finite point coordinates");
let mut hash_builder = DefaultHasher::new();
coord.hash_coordinate(&mut hash_builder);
hashes.insert(hash_builder.finish());
}
assert_eq!(
hashes.len(),
test_coords.len(),
"Hash collision detected in basic test set"
);
}
#[test]
fn coordinate_constants_correctness() {
const _F64_POSITIVE: () = assert!(DEFAULT_TOLERANCE_F64 > 0.0);
assert_relative_eq!(DEFAULT_TOLERANCE_F64, 1e-15, epsilon = f64::EPSILON);
}
#[test]
fn coordinate_f64_helper_traits_are_consistent() {
const _: () = assert!(DEFAULT_TOLERANCE_F64 > 0.0);
let zero = 0.0_f64;
let nan = f64::NAN;
assert!(zero.ordered_eq(&0.0));
assert!(nan.ordered_eq(&f64::NAN));
assert!(zero.is_finite_generic());
assert!(!nan.is_finite_generic());
assert_relative_eq!(f64::default(), 0.0, epsilon = f64::EPSILON);
assert_eq!(F64_MANTISSA_DIGITS, 53);
}
#[test]
fn coordinate_validation_error_source_trait() {
let error = CoordinateValidationError::InvalidCoordinate {
coordinate_index: 1,
coordinate_value: InvalidCoordinateValue::Nan,
dimension: 3,
};
assert!(error.source().is_none());
assert!(Error::source(&error).is_none());
assert_eq!(format!("{error}"), error.to_string());
}
#[test]
fn coordinate_trait_dimension_consistency() {
const DIM_1D: usize = 1;
const DIM_7D: usize = 7;
let coord_1d: Point<1> = Point::try_new([42.0]).expect("finite point coordinates");
assert_eq!(coord_1d.dim(), 1);
assert_eq!(coord_1d.to_array().len(), 1);
let coord_7d: Point<7> =
Point::try_new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]).expect("finite point coordinates");
assert_eq!(coord_7d.dim(), 7);
assert_eq!(coord_7d.to_array().len(), 7);
assert_eq!(coord_1d.dim(), DIM_1D);
assert_eq!(coord_7d.dim(), DIM_7D);
}
}