use crate::{UtilsError, UtilsResult};
use scirs2_core::ndarray::Array1;
use scirs2_core::numeric::{Float, FromPrimitive};
use std::cmp::Ordering;
pub mod constants {
pub const PI: f64 = std::f64::consts::PI;
pub const E: f64 = std::f64::consts::E;
pub const LN_2: f64 = std::f64::consts::LN_2;
pub const LN_10: f64 = std::f64::consts::LN_10;
pub const SQRT_2: f64 = std::f64::consts::SQRT_2;
pub const SQRT_PI: f64 = 1.772_453_850_905_516;
pub const EPS_F32: f32 = f32::EPSILON;
pub const EPS_F64: f64 = f64::EPSILON;
pub const TINY_F32: f32 = 1e-30;
pub const TINY_F64: f64 = 1e-100;
pub const HUGE_F32: f32 = 1e30;
pub const HUGE_F64: f64 = 1e100;
}
pub struct NumericalPrecision;
impl NumericalPrecision {
pub fn epsilon<T: Float>() -> T {
T::epsilon()
}
pub fn tiny<T: Float>() -> T {
T::from(1e-30).unwrap_or_else(|| T::epsilon())
}
pub fn huge<T: Float>() -> T {
T::from(1e30).unwrap_or_else(|| T::max_value())
}
pub fn is_zero<T: Float>(value: T, eps: Option<T>) -> bool {
let tolerance =
eps.unwrap_or_else(|| T::epsilon() * T::from(10).expect("operation should succeed"));
value.abs() < tolerance
}
pub fn approx_eq<T: Float>(a: T, b: T, eps: Option<T>) -> bool {
let tolerance =
eps.unwrap_or_else(|| T::epsilon() * T::from(10).expect("operation should succeed"));
(a - b).abs() < tolerance
}
pub fn rel_eq<T: Float>(a: T, b: T, rel_tol: Option<T>) -> bool {
let tolerance = rel_tol.unwrap_or_else(|| T::from(1e-9).expect("operation should succeed"));
let max_val = a.abs().max(b.abs());
if max_val < T::epsilon() {
return true; }
(a - b).abs() / max_val < tolerance
}
pub fn safe_cmp<T: Float>(a: T, b: T, eps: Option<T>) -> Ordering {
if Self::approx_eq(a, b, eps) {
Ordering::Equal
} else if a < b {
Ordering::Less
} else {
Ordering::Greater
}
}
}
pub struct OverflowDetection;
impl OverflowDetection {
pub fn near_overflow<T: Float>(value: T) -> bool {
let max_val = T::max_value();
value.abs() > max_val / T::from(1000).expect("operation should succeed")
}
pub fn near_underflow<T: Float>(value: T) -> bool {
let min_val = T::min_positive_value();
value.abs() < min_val * T::from(10).expect("operation should succeed") && !value.is_zero()
}
pub fn safe_add<T: Float>(a: T, b: T) -> UtilsResult<T> {
if Self::near_overflow(a) || Self::near_overflow(b) {
return Err(UtilsError::InvalidParameter(
"Addition would cause overflow".to_string(),
));
}
let result = a + b;
if !result.is_finite() {
return Err(UtilsError::InvalidParameter(
"Addition resulted in non-finite value".to_string(),
));
}
Ok(result)
}
pub fn safe_mul<T: Float>(a: T, b: T) -> UtilsResult<T> {
if Self::near_overflow(a) && !NumericalPrecision::is_zero(b, None) {
return Err(UtilsError::InvalidParameter(
"Multiplication would cause overflow".to_string(),
));
}
let result = a * b;
if !result.is_finite() {
return Err(UtilsError::InvalidParameter(
"Multiplication resulted in non-finite value".to_string(),
));
}
Ok(result)
}
pub fn safe_div<T: Float>(a: T, b: T) -> UtilsResult<T> {
if NumericalPrecision::is_zero(b, None) {
return Err(UtilsError::InvalidParameter("Division by zero".to_string()));
}
if Self::near_underflow(b) && !NumericalPrecision::is_zero(a, None) {
return Err(UtilsError::InvalidParameter(
"Division would cause overflow".to_string(),
));
}
let result = a / b;
if !result.is_finite() {
return Err(UtilsError::InvalidParameter(
"Division resulted in non-finite value".to_string(),
));
}
Ok(result)
}
}
pub struct SpecialFunctions;
impl SpecialFunctions {
pub fn logistic<T: Float>(x: T) -> T {
let one = T::one();
one / (one + (-x).exp())
}
pub fn logsumexp<T: Float>(x: &[T]) -> T {
if x.is_empty() {
return T::neg_infinity();
}
let max_val = x.iter().copied().fold(T::neg_infinity(), T::max);
if !max_val.is_finite() {
return max_val;
}
let sum_exp: T = x
.iter()
.map(|&val| (val - max_val).exp())
.fold(T::zero(), |acc, val| acc + val);
max_val + sum_exp.ln()
}
pub fn softmax<T: Float>(x: &[T]) -> Vec<T> {
if x.is_empty() {
return Vec::new();
}
let max_val = x.iter().copied().fold(T::neg_infinity(), T::max);
let exp_vals: Vec<T> = x.iter().map(|&val| (val - max_val).exp()).collect();
let sum_exp: T = exp_vals
.iter()
.copied()
.fold(T::zero(), |acc, val| acc + val);
exp_vals.into_iter().map(|val| val / sum_exp).collect()
}
pub fn log_softmax<T: Float>(x: &[T]) -> Vec<T> {
let log_sum_exp = Self::logsumexp(x);
x.iter().map(|&val| val - log_sum_exp).collect()
}
pub fn gamma(x: f64) -> f64 {
if x == 1.0 || x == 2.0 {
1.0
} else if x == 3.0 {
2.0
} else if x == 4.0 {
6.0
} else if x > 1.0 {
(x - 1.0) * Self::gamma(x - 1.0)
} else {
1.0 / x }
}
pub fn lgamma(x: f64) -> f64 {
Self::gamma(x).ln()
}
pub fn gamma_inc(a: f64, x: f64) -> f64 {
if x < 0.0 || a <= 0.0 {
return 0.0;
}
if x < a + 1.0 {
let mut sum = 1.0;
let mut term = 1.0;
let mut n = 1.0;
for _ in 0..100 {
term *= x / (a + n - 1.0);
sum += term;
if term.abs() < 1e-15 {
break;
}
n += 1.0;
}
sum * x.powf(a) * (-x).exp() / Self::gamma(a)
} else {
Self::gamma(a) * (1.0 - Self::gamma_inc_cf(a, x))
}
}
fn gamma_inc_cf(a: f64, x: f64) -> f64 {
let mut b = x + 1.0 - a;
let mut c = 1e30;
let mut d = 1.0 / b;
let mut h = d;
for i in 1..=100 {
let an = -i as f64 * (i as f64 - a);
b += 2.0;
d = an * d + b;
if d.abs() < 1e-30 {
d = 1e-30;
}
c = b + an / c;
if c.abs() < 1e-30 {
c = 1e-30;
}
d = 1.0 / d;
let del = d * c;
h *= del;
if (del - 1.0).abs() < 1e-15 {
break;
}
}
h * x.powf(a) * (-x).exp()
}
pub fn beta(a: f64, b: f64) -> f64 {
(Self::gamma(a) * Self::gamma(b)) / Self::gamma(a + b)
}
pub fn erf(x: f64) -> f64 {
const A1: f64 = 0.254829592;
const A2: f64 = -0.284496736;
const A3: f64 = 1.421413741;
const A4: f64 = -1.453152027;
const A5: f64 = 1.061405429;
const P: f64 = 0.3275911;
let sign = if x >= 0.0 { 1.0 } else { -1.0 };
let x = x.abs();
let t = 1.0 / (1.0 + P * x);
let y = 1.0 - (((((A5 * t + A4) * t) + A3) * t + A2) * t + A1) * t * (-x * x).exp();
sign * y
}
pub fn erfc(x: f64) -> f64 {
1.0 - Self::erf(x)
}
}
pub struct RobustArrayOps;
impl RobustArrayOps {
pub fn robust_sum<T: Float + FromPrimitive>(arr: &Array1<T>) -> T {
let mut sum = T::zero();
let mut c = T::zero();
for &value in arr.iter() {
let y = value - c;
let t = sum + y;
c = (t - sum) - y;
sum = t;
}
sum
}
pub fn robust_mean<T: Float + FromPrimitive>(arr: &Array1<T>) -> UtilsResult<T> {
if arr.is_empty() {
return Err(UtilsError::EmptyInput);
}
let sum = Self::robust_sum(arr);
let n = T::from(arr.len()).expect("operation should succeed");
OverflowDetection::safe_div(sum, n)
}
pub fn robust_variance<T: Float + FromPrimitive>(
arr: &Array1<T>,
ddof: usize,
) -> UtilsResult<T> {
if arr.len() <= ddof {
return Err(UtilsError::InsufficientData {
min: ddof + 1,
actual: arr.len(),
});
}
let mean = Self::robust_mean(arr)?;
let mut sum_sq = T::zero();
let mut c = T::zero();
for &value in arr.iter() {
let diff = value - mean;
let sq_diff = diff * diff;
let y = sq_diff - c;
let t = sum_sq + y;
c = (t - sum_sq) - y;
sum_sq = t;
}
let n = T::from(arr.len() - ddof).expect("operation should succeed");
OverflowDetection::safe_div(sum_sq, n)
}
pub fn robust_std<T: Float + FromPrimitive>(arr: &Array1<T>, ddof: usize) -> UtilsResult<T> {
let variance = Self::robust_variance(arr, ddof)?;
Ok(variance.sqrt())
}
pub fn robust_dot<T: Float + FromPrimitive>(a: &Array1<T>, b: &Array1<T>) -> UtilsResult<T> {
if a.len() != b.len() {
return Err(UtilsError::ShapeMismatch {
expected: vec![a.len()],
actual: vec![b.len()],
});
}
let mut sum = T::zero();
let mut c = T::zero();
for (&x, &y) in a.iter().zip(b.iter()) {
let product = OverflowDetection::safe_mul(x, y)?;
let corrected = product - c;
let temp = sum + corrected;
c = (temp - sum) - corrected;
sum = temp;
}
Ok(sum)
}
pub fn robust_norm<T: Float + FromPrimitive>(arr: &Array1<T>) -> UtilsResult<T> {
if arr.is_empty() {
return Ok(T::zero());
}
let max_abs = arr.iter().map(|&x| x.abs()).fold(T::zero(), T::max);
if NumericalPrecision::is_zero(max_abs, None) {
return Ok(T::zero());
}
let mut sum_sq = T::zero();
let mut c = T::zero();
for &value in arr.iter() {
let scaled = OverflowDetection::safe_div(value, max_abs)?;
let sq = OverflowDetection::safe_mul(scaled, scaled)?;
let y = sq - c;
let t = sum_sq + y;
c = (t - sum_sq) - y;
sum_sq = t;
}
let norm_scaled = sum_sq.sqrt();
OverflowDetection::safe_mul(norm_scaled, max_abs)
}
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_numerical_precision() {
assert!(NumericalPrecision::is_zero(1e-16, None));
assert!(!NumericalPrecision::is_zero(1e-6, None));
assert!(NumericalPrecision::approx_eq(1.0, 1.0 + 1e-15, None));
assert!(!NumericalPrecision::approx_eq(1.0, 1.1, None));
assert!(NumericalPrecision::rel_eq(1000.0, 1000.0001, Some(1e-6)));
assert!(!NumericalPrecision::rel_eq(1000.0, 1001.0, Some(1e-6)));
}
#[test]
fn test_overflow_detection() {
assert!(OverflowDetection::safe_add(f64::MAX / 2.0, f64::MAX / 2.0).is_err());
assert!(OverflowDetection::safe_add(1.0, 2.0).is_ok());
assert!(OverflowDetection::safe_mul(f64::MAX / 2.0, 2.0).is_err());
assert!(OverflowDetection::safe_mul(2.0, 3.0).is_ok());
assert!(OverflowDetection::safe_div(1.0, 0.0).is_err());
assert!(OverflowDetection::safe_div(1.0, f64::MIN_POSITIVE).is_err());
assert_relative_eq!(
OverflowDetection::safe_div(6.0, 2.0).expect("operation should succeed"),
3.0
);
}
#[test]
fn test_special_functions() {
assert_relative_eq!(SpecialFunctions::logistic(0.0), 0.5, epsilon = 1e-10);
assert!(SpecialFunctions::logistic(10.0) > 0.99);
assert!(SpecialFunctions::logistic(-10.0) < 0.01);
let x = [1.0, 2.0, 3.0];
let result = SpecialFunctions::logsumexp(&x);
let expected = (1.0_f64.exp() + 2.0_f64.exp() + 3.0_f64.exp()).ln();
assert_relative_eq!(result, expected, epsilon = 1e-10);
let softmax_result = SpecialFunctions::softmax(&x);
let sum: f64 = softmax_result.iter().sum();
assert_relative_eq!(sum, 1.0, epsilon = 1e-10);
assert_relative_eq!(SpecialFunctions::gamma(1.0), 1.0, epsilon = 1e-8);
assert_relative_eq!(SpecialFunctions::gamma(2.0), 1.0, epsilon = 1e-8);
assert_relative_eq!(SpecialFunctions::gamma(3.0), 2.0, epsilon = 1e-8);
assert_relative_eq!(SpecialFunctions::gamma(4.0), 6.0, epsilon = 1e-8);
assert_relative_eq!(SpecialFunctions::erf(0.0), 0.0, epsilon = 1e-9);
assert!(SpecialFunctions::erf(1.0) > 0.8);
assert!(SpecialFunctions::erf(-1.0) < -0.8);
}
#[test]
fn test_robust_array_ops() {
let arr = array![1.0, 2.0, 3.0, 4.0, 5.0];
let sum = RobustArrayOps::robust_sum(&arr);
assert_relative_eq!(sum, 15.0, epsilon = 1e-10);
let mean = RobustArrayOps::robust_mean(&arr).expect("operation should succeed");
assert_relative_eq!(mean, 3.0, epsilon = 1e-10);
let var = RobustArrayOps::robust_variance(&arr, 1).expect("operation should succeed");
assert_relative_eq!(var, 2.5, epsilon = 1e-10);
let std = RobustArrayOps::robust_std(&arr, 1).expect("operation should succeed");
assert_relative_eq!(std, 2.5_f64.sqrt(), epsilon = 1e-10);
let a = array![1.0, 2.0, 3.0];
let b = array![4.0, 5.0, 6.0];
let dot = RobustArrayOps::robust_dot(&a, &b).expect("operation should succeed");
assert_relative_eq!(dot, 32.0, epsilon = 1e-10);
let norm = RobustArrayOps::robust_norm(&a).expect("operation should succeed");
let expected_norm = (1.0 + 4.0 + 9.0_f64).sqrt(); assert_relative_eq!(norm, expected_norm, epsilon = 1e-10);
}
}