use crate::error::SolverError;
use crate::scalar::{BaseScalar, DivisibleScalar, FiniteScalar, MetricScalar, OrderedScalar};
fn base_laws<S: BaseScalar + core::fmt::Debug>() {
assert!(S::zero().is_zero());
assert!(!S::one().is_zero());
assert_eq!(S::zero().add(S::one()), S::one()); assert_eq!(S::one().mul(S::one()), S::one()); let two = S::one().add(S::one());
assert_eq!(two.sub(S::one()), S::one());
assert_eq!(S::one().neg().neg(), S::one()); assert!(S::one().add(S::one().neg()).is_zero()); }
#[test]
fn base_algebra_holds_for_primitives() {
base_laws::<f32>();
base_laws::<f64>();
}
#[test]
fn negative_zero_is_zero() {
assert!((-0.0f32).is_zero());
assert!((-0.0f64).is_zero());
}
fn ordering_laws<S: OrderedScalar + core::fmt::Debug>(a: S, b: S) {
assert_eq!(a.min(b), b.min(a)); assert_eq!(a.max(b), b.max(a));
assert_eq!(a.min(a), a); assert_eq!(a.max(a), a);
let (lo, hi) = if a <= b { (a, b) } else { (b, a) };
assert_eq!(a.min(b), lo);
assert_eq!(a.max(b), hi);
}
#[test]
fn ordering_matches_total_order() {
ordering_laws::<f32>(2.0, 5.0);
ordering_laws::<f32>(5.0, 2.0);
ordering_laws::<f64>(-3.0, 7.5);
}
#[test]
fn extrema_propagate_nan() {
assert!(OrderedScalar::min(f64::NAN, 1.0).is_nan());
assert!(OrderedScalar::min(1.0_f64, f64::NAN).is_nan());
assert!(OrderedScalar::max(f64::NAN, 1.0).is_nan());
assert!(OrderedScalar::max(1.0_f64, f64::NAN).is_nan());
assert!(OrderedScalar::min(f32::NAN, 1.0).is_nan());
assert!(OrderedScalar::max(1.0_f32, f32::NAN).is_nan());
}
#[test]
fn clamp_within_bounds_and_panic_free() {
assert_eq!(OrderedScalar::clamp(5.0_f64, 0.0, 10.0), 5.0);
assert_eq!(OrderedScalar::clamp(-2.0_f64, 0.0, 10.0), 0.0);
assert_eq!(OrderedScalar::clamp(99.0_f64, 0.0, 10.0), 10.0);
assert_eq!(OrderedScalar::clamp(3.0_f32, 1.0, 4.0), 3.0);
assert_eq!(OrderedScalar::clamp(0.0_f32, 1.0, 4.0), 1.0);
}
#[test]
fn clamp_with_inverted_bounds_returns_hi_without_panic() {
assert_eq!(OrderedScalar::clamp(5.0_f64, 10.0, 0.0), 0.0);
}
#[test]
fn clamp_propagates_nan() {
assert!(OrderedScalar::clamp(f64::NAN, 0.0, 1.0).is_nan());
assert!(OrderedScalar::clamp(0.5_f64, f64::NAN, 1.0).is_nan());
assert!(OrderedScalar::clamp(0.5_f64, 0.0, f64::NAN).is_nan());
}
#[test]
fn finite_checks_are_mutually_exclusive() {
for x in [
0.0_f64,
-1.5,
1e300,
f64::NAN,
f64::INFINITY,
f64::NEG_INFINITY,
] {
let f = FiniteScalar::is_finite(x);
let n = FiniteScalar::is_nan(x);
let i = FiniteScalar::is_infinite(x);
assert_eq!([f, n, i].into_iter().filter(|b| *b).count(), 1, "x={x}");
}
assert!(FiniteScalar::is_finite(0.0_f32));
assert!(FiniteScalar::is_nan(f32::NAN));
assert!(FiniteScalar::is_infinite(f32::INFINITY));
}
#[test]
fn division_by_zero_is_an_error() {
assert_eq!(1.0_f64.checked_div(0.0), Err(SolverError::NumericalDomain));
assert_eq!(1.0_f64.checked_div(-0.0), Err(SolverError::NumericalDomain));
assert_eq!(1.0_f64.checked_recip().map(|_| ()), Ok(()));
assert_eq!(0.0_f64.checked_recip(), Err(SolverError::NumericalDomain));
}
#[test]
fn finite_nonzero_division_succeeds() {
assert_eq!(6.0_f64.checked_div(2.0), Ok(3.0));
assert_eq!(1.0_f32.checked_div(4.0), Ok(0.25));
}
#[test]
fn division_overflowing_to_non_finite_is_an_error() {
assert_eq!(
f64::MAX.checked_div(f64::MIN_POSITIVE),
Err(SolverError::Overflow)
);
}
#[test]
fn division_with_non_finite_operands_does_not_yield_ok_non_finite() {
assert!(f64::NAN.checked_div(2.0).is_err());
assert!(2.0_f64.checked_div(f64::NAN).is_err());
assert!(f64::INFINITY.checked_div(2.0).is_err());
assert_eq!(2.0_f64.checked_div(f64::INFINITY), Ok(0.0));
}
#[test]
fn recip_agrees_with_one_over_x() {
for x in [1.0_f64, -2.0, 0.5, 1234.5] {
assert_eq!(x.checked_recip(), f64::one().checked_div(x));
}
}
#[test]
fn abs_is_nonnegative_for_finite_values() {
for x in [0.0_f64, -3.5, 7.0, -1e9] {
assert!(MetricScalar::abs(x) >= 0.0);
}
assert_eq!(MetricScalar::abs(-4.0_f32), 4.0);
}
#[test]
fn lte_tolerance_uses_magnitude() {
assert!((0.05_f64).lte_tolerance(0.1));
assert!((-0.05_f64).lte_tolerance(0.1));
assert!(!(0.2_f64).lte_tolerance(0.1));
}
#[test]
fn epsilon_is_positive() {
assert!(<f64 as MetricScalar>::epsilon() > 0.0);
assert!(<f32 as MetricScalar>::epsilon() > 0.0);
}