1use crate::{UtilsError, UtilsResult};
4use scirs2_core::ndarray::Array1;
5use scirs2_core::numeric::{Float, FromPrimitive};
6use std::cmp::Ordering;
7
8pub mod constants {
10 pub const PI: f64 = std::f64::consts::PI;
11 pub const E: f64 = std::f64::consts::E;
12 pub const LN_2: f64 = std::f64::consts::LN_2;
13 pub const LN_10: f64 = std::f64::consts::LN_10;
14 pub const SQRT_2: f64 = std::f64::consts::SQRT_2;
15 pub const SQRT_PI: f64 = 1.772_453_850_905_516;
16 pub const EPS_F32: f32 = f32::EPSILON;
17 pub const EPS_F64: f64 = f64::EPSILON;
18 pub const TINY_F32: f32 = 1e-30;
19 pub const TINY_F64: f64 = 1e-100;
20 pub const HUGE_F32: f32 = 1e30;
21 pub const HUGE_F64: f64 = 1e100;
22}
23
24pub struct NumericalPrecision;
26
27impl NumericalPrecision {
28 pub fn epsilon<T: Float>() -> T {
30 T::epsilon()
31 }
32
33 pub fn tiny<T: Float>() -> T {
35 T::from(1e-30).unwrap_or_else(|| T::epsilon())
36 }
37
38 pub fn huge<T: Float>() -> T {
40 T::from(1e30).unwrap_or_else(|| T::max_value())
41 }
42
43 pub fn is_zero<T: Float>(value: T, eps: Option<T>) -> bool {
45 let tolerance =
46 eps.unwrap_or_else(|| T::epsilon() * T::from(10).expect("operation should succeed"));
47 value.abs() < tolerance
48 }
49
50 pub fn approx_eq<T: Float>(a: T, b: T, eps: Option<T>) -> bool {
52 let tolerance =
53 eps.unwrap_or_else(|| T::epsilon() * T::from(10).expect("operation should succeed"));
54 (a - b).abs() < tolerance
55 }
56
57 pub fn rel_eq<T: Float>(a: T, b: T, rel_tol: Option<T>) -> bool {
59 let tolerance = rel_tol.unwrap_or_else(|| T::from(1e-9).expect("operation should succeed"));
60 let max_val = a.abs().max(b.abs());
61 if max_val < T::epsilon() {
62 return true; }
64 (a - b).abs() / max_val < tolerance
65 }
66
67 pub fn safe_cmp<T: Float>(a: T, b: T, eps: Option<T>) -> Ordering {
69 if Self::approx_eq(a, b, eps) {
70 Ordering::Equal
71 } else if a < b {
72 Ordering::Less
73 } else {
74 Ordering::Greater
75 }
76 }
77}
78
79pub struct OverflowDetection;
81
82impl OverflowDetection {
83 pub fn near_overflow<T: Float>(value: T) -> bool {
85 let max_val = T::max_value();
86 value.abs() > max_val / T::from(1000).expect("operation should succeed")
87 }
88
89 pub fn near_underflow<T: Float>(value: T) -> bool {
91 let min_val = T::min_positive_value();
92 value.abs() < min_val * T::from(10).expect("operation should succeed") && !value.is_zero()
93 }
94
95 pub fn safe_add<T: Float>(a: T, b: T) -> UtilsResult<T> {
97 if Self::near_overflow(a) || Self::near_overflow(b) {
98 return Err(UtilsError::InvalidParameter(
99 "Addition would cause overflow".to_string(),
100 ));
101 }
102 let result = a + b;
103 if !result.is_finite() {
104 return Err(UtilsError::InvalidParameter(
105 "Addition resulted in non-finite value".to_string(),
106 ));
107 }
108 Ok(result)
109 }
110
111 pub fn safe_mul<T: Float>(a: T, b: T) -> UtilsResult<T> {
113 if Self::near_overflow(a) && !NumericalPrecision::is_zero(b, None) {
114 return Err(UtilsError::InvalidParameter(
115 "Multiplication would cause overflow".to_string(),
116 ));
117 }
118 let result = a * b;
119 if !result.is_finite() {
120 return Err(UtilsError::InvalidParameter(
121 "Multiplication resulted in non-finite value".to_string(),
122 ));
123 }
124 Ok(result)
125 }
126
127 pub fn safe_div<T: Float>(a: T, b: T) -> UtilsResult<T> {
129 if NumericalPrecision::is_zero(b, None) {
130 return Err(UtilsError::InvalidParameter("Division by zero".to_string()));
131 }
132 if Self::near_underflow(b) && !NumericalPrecision::is_zero(a, None) {
133 return Err(UtilsError::InvalidParameter(
134 "Division would cause overflow".to_string(),
135 ));
136 }
137 let result = a / b;
138 if !result.is_finite() {
139 return Err(UtilsError::InvalidParameter(
140 "Division resulted in non-finite value".to_string(),
141 ));
142 }
143 Ok(result)
144 }
145}
146
147pub struct SpecialFunctions;
149
150impl SpecialFunctions {
151 pub fn logistic<T: Float>(x: T) -> T {
153 let one = T::one();
154 one / (one + (-x).exp())
155 }
156
157 pub fn logsumexp<T: Float>(x: &[T]) -> T {
159 if x.is_empty() {
160 return T::neg_infinity();
161 }
162
163 let max_val = x.iter().copied().fold(T::neg_infinity(), T::max);
164 if !max_val.is_finite() {
165 return max_val;
166 }
167
168 let sum_exp: T = x
169 .iter()
170 .map(|&val| (val - max_val).exp())
171 .fold(T::zero(), |acc, val| acc + val);
172
173 max_val + sum_exp.ln()
174 }
175
176 pub fn softmax<T: Float>(x: &[T]) -> Vec<T> {
178 if x.is_empty() {
179 return Vec::new();
180 }
181
182 let max_val = x.iter().copied().fold(T::neg_infinity(), T::max);
183 let exp_vals: Vec<T> = x.iter().map(|&val| (val - max_val).exp()).collect();
184
185 let sum_exp: T = exp_vals
186 .iter()
187 .copied()
188 .fold(T::zero(), |acc, val| acc + val);
189
190 exp_vals.into_iter().map(|val| val / sum_exp).collect()
191 }
192
193 pub fn log_softmax<T: Float>(x: &[T]) -> Vec<T> {
195 let log_sum_exp = Self::logsumexp(x);
196 x.iter().map(|&val| val - log_sum_exp).collect()
197 }
198
199 pub fn gamma(x: f64) -> f64 {
201 if x == 1.0 || x == 2.0 {
203 1.0
204 } else if x == 3.0 {
205 2.0
206 } else if x == 4.0 {
207 6.0
208 } else if x > 1.0 {
209 (x - 1.0) * Self::gamma(x - 1.0)
211 } else {
212 1.0 / x }
215 }
216
217 pub fn lgamma(x: f64) -> f64 {
219 Self::gamma(x).ln()
220 }
221
222 pub fn gamma_inc(a: f64, x: f64) -> f64 {
224 if x < 0.0 || a <= 0.0 {
225 return 0.0;
226 }
227
228 if x < a + 1.0 {
230 let mut sum = 1.0;
231 let mut term = 1.0;
232 let mut n = 1.0;
233
234 for _ in 0..100 {
235 term *= x / (a + n - 1.0);
236 sum += term;
237 if term.abs() < 1e-15 {
238 break;
239 }
240 n += 1.0;
241 }
242
243 sum * x.powf(a) * (-x).exp() / Self::gamma(a)
244 } else {
245 Self::gamma(a) * (1.0 - Self::gamma_inc_cf(a, x))
247 }
248 }
249
250 fn gamma_inc_cf(a: f64, x: f64) -> f64 {
252 let mut b = x + 1.0 - a;
253 let mut c = 1e30;
254 let mut d = 1.0 / b;
255 let mut h = d;
256
257 for i in 1..=100 {
258 let an = -i as f64 * (i as f64 - a);
259 b += 2.0;
260 d = an * d + b;
261 if d.abs() < 1e-30 {
262 d = 1e-30;
263 }
264 c = b + an / c;
265 if c.abs() < 1e-30 {
266 c = 1e-30;
267 }
268 d = 1.0 / d;
269 let del = d * c;
270 h *= del;
271 if (del - 1.0).abs() < 1e-15 {
272 break;
273 }
274 }
275
276 h * x.powf(a) * (-x).exp()
277 }
278
279 pub fn beta(a: f64, b: f64) -> f64 {
281 (Self::gamma(a) * Self::gamma(b)) / Self::gamma(a + b)
282 }
283
284 pub fn erf(x: f64) -> f64 {
286 const A1: f64 = 0.254829592;
288 const A2: f64 = -0.284496736;
289 const A3: f64 = 1.421413741;
290 const A4: f64 = -1.453152027;
291 const A5: f64 = 1.061405429;
292 const P: f64 = 0.3275911;
293
294 let sign = if x >= 0.0 { 1.0 } else { -1.0 };
295 let x = x.abs();
296
297 let t = 1.0 / (1.0 + P * x);
298 let y = 1.0 - (((((A5 * t + A4) * t) + A3) * t + A2) * t + A1) * t * (-x * x).exp();
299
300 sign * y
301 }
302
303 pub fn erfc(x: f64) -> f64 {
305 1.0 - Self::erf(x)
306 }
307}
308
309pub struct RobustArrayOps;
311
312impl RobustArrayOps {
313 pub fn robust_sum<T: Float + FromPrimitive>(arr: &Array1<T>) -> T {
315 let mut sum = T::zero();
317 let mut c = T::zero(); for &value in arr.iter() {
320 let y = value - c;
321 let t = sum + y;
322 c = (t - sum) - y;
323 sum = t;
324 }
325
326 sum
327 }
328
329 pub fn robust_mean<T: Float + FromPrimitive>(arr: &Array1<T>) -> UtilsResult<T> {
331 if arr.is_empty() {
332 return Err(UtilsError::EmptyInput);
333 }
334
335 let sum = Self::robust_sum(arr);
336 let n = T::from(arr.len()).expect("operation should succeed");
337
338 OverflowDetection::safe_div(sum, n)
339 }
340
341 pub fn robust_variance<T: Float + FromPrimitive>(
343 arr: &Array1<T>,
344 ddof: usize,
345 ) -> UtilsResult<T> {
346 if arr.len() <= ddof {
347 return Err(UtilsError::InsufficientData {
348 min: ddof + 1,
349 actual: arr.len(),
350 });
351 }
352
353 let mean = Self::robust_mean(arr)?;
354 let mut sum_sq = T::zero();
355 let mut c = T::zero(); for &value in arr.iter() {
358 let diff = value - mean;
359 let sq_diff = diff * diff;
360 let y = sq_diff - c;
361 let t = sum_sq + y;
362 c = (t - sum_sq) - y;
363 sum_sq = t;
364 }
365
366 let n = T::from(arr.len() - ddof).expect("operation should succeed");
367 OverflowDetection::safe_div(sum_sq, n)
368 }
369
370 pub fn robust_std<T: Float + FromPrimitive>(arr: &Array1<T>, ddof: usize) -> UtilsResult<T> {
372 let variance = Self::robust_variance(arr, ddof)?;
373 Ok(variance.sqrt())
374 }
375
376 pub fn robust_dot<T: Float + FromPrimitive>(a: &Array1<T>, b: &Array1<T>) -> UtilsResult<T> {
378 if a.len() != b.len() {
379 return Err(UtilsError::ShapeMismatch {
380 expected: vec![a.len()],
381 actual: vec![b.len()],
382 });
383 }
384
385 let mut sum = T::zero();
386 let mut c = T::zero(); for (&x, &y) in a.iter().zip(b.iter()) {
389 let product = OverflowDetection::safe_mul(x, y)?;
390 let corrected = product - c;
391 let temp = sum + corrected;
392 c = (temp - sum) - corrected;
393 sum = temp;
394 }
395
396 Ok(sum)
397 }
398
399 pub fn robust_norm<T: Float + FromPrimitive>(arr: &Array1<T>) -> UtilsResult<T> {
401 if arr.is_empty() {
402 return Ok(T::zero());
403 }
404
405 let max_abs = arr.iter().map(|&x| x.abs()).fold(T::zero(), T::max);
407
408 if NumericalPrecision::is_zero(max_abs, None) {
409 return Ok(T::zero());
410 }
411
412 let mut sum_sq = T::zero();
413 let mut c = T::zero(); for &value in arr.iter() {
416 let scaled = OverflowDetection::safe_div(value, max_abs)?;
417 let sq = OverflowDetection::safe_mul(scaled, scaled)?;
418 let y = sq - c;
419 let t = sum_sq + y;
420 c = (t - sum_sq) - y;
421 sum_sq = t;
422 }
423
424 let norm_scaled = sum_sq.sqrt();
425 OverflowDetection::safe_mul(norm_scaled, max_abs)
426 }
427}
428
429#[allow(non_snake_case)]
430#[cfg(test)]
431mod tests {
432 use super::*;
433 use approx::assert_relative_eq;
434 use scirs2_core::ndarray::array;
435
436 #[test]
437 fn test_numerical_precision() {
438 assert!(NumericalPrecision::is_zero(1e-16, None));
439 assert!(!NumericalPrecision::is_zero(1e-6, None));
440
441 assert!(NumericalPrecision::approx_eq(1.0, 1.0 + 1e-15, None));
442 assert!(!NumericalPrecision::approx_eq(1.0, 1.1, None));
443
444 assert!(NumericalPrecision::rel_eq(1000.0, 1000.0001, Some(1e-6)));
445 assert!(!NumericalPrecision::rel_eq(1000.0, 1001.0, Some(1e-6)));
446 }
447
448 #[test]
449 fn test_overflow_detection() {
450 assert!(OverflowDetection::safe_add(f64::MAX / 2.0, f64::MAX / 2.0).is_err());
452 assert!(OverflowDetection::safe_add(1.0, 2.0).is_ok());
453
454 assert!(OverflowDetection::safe_mul(f64::MAX / 2.0, 2.0).is_err());
455 assert!(OverflowDetection::safe_mul(2.0, 3.0).is_ok());
456
457 assert!(OverflowDetection::safe_div(1.0, 0.0).is_err());
458 assert!(OverflowDetection::safe_div(1.0, f64::MIN_POSITIVE).is_err());
459 assert_relative_eq!(
460 OverflowDetection::safe_div(6.0, 2.0).expect("operation should succeed"),
461 3.0
462 );
463 }
464
465 #[test]
466 fn test_special_functions() {
467 assert_relative_eq!(SpecialFunctions::logistic(0.0), 0.5, epsilon = 1e-10);
469 assert!(SpecialFunctions::logistic(10.0) > 0.99);
470 assert!(SpecialFunctions::logistic(-10.0) < 0.01);
471
472 let x = [1.0, 2.0, 3.0];
474 let result = SpecialFunctions::logsumexp(&x);
475 let expected = (1.0_f64.exp() + 2.0_f64.exp() + 3.0_f64.exp()).ln();
476 assert_relative_eq!(result, expected, epsilon = 1e-10);
477
478 let softmax_result = SpecialFunctions::softmax(&x);
480 let sum: f64 = softmax_result.iter().sum();
481 assert_relative_eq!(sum, 1.0, epsilon = 1e-10);
482
483 assert_relative_eq!(SpecialFunctions::gamma(1.0), 1.0, epsilon = 1e-8);
485 assert_relative_eq!(SpecialFunctions::gamma(2.0), 1.0, epsilon = 1e-8);
486 assert_relative_eq!(SpecialFunctions::gamma(3.0), 2.0, epsilon = 1e-8);
487 assert_relative_eq!(SpecialFunctions::gamma(4.0), 6.0, epsilon = 1e-8);
488
489 assert_relative_eq!(SpecialFunctions::erf(0.0), 0.0, epsilon = 1e-9);
491 assert!(SpecialFunctions::erf(1.0) > 0.8);
492 assert!(SpecialFunctions::erf(-1.0) < -0.8);
493 }
494
495 #[test]
496 fn test_robust_array_ops() {
497 let arr = array![1.0, 2.0, 3.0, 4.0, 5.0];
498
499 let sum = RobustArrayOps::robust_sum(&arr);
501 assert_relative_eq!(sum, 15.0, epsilon = 1e-10);
502
503 let mean = RobustArrayOps::robust_mean(&arr).expect("operation should succeed");
505 assert_relative_eq!(mean, 3.0, epsilon = 1e-10);
506
507 let var = RobustArrayOps::robust_variance(&arr, 1).expect("operation should succeed");
509 assert_relative_eq!(var, 2.5, epsilon = 1e-10);
510
511 let std = RobustArrayOps::robust_std(&arr, 1).expect("operation should succeed");
513 assert_relative_eq!(std, 2.5_f64.sqrt(), epsilon = 1e-10);
514
515 let a = array![1.0, 2.0, 3.0];
517 let b = array![4.0, 5.0, 6.0];
518 let dot = RobustArrayOps::robust_dot(&a, &b).expect("operation should succeed");
519 assert_relative_eq!(dot, 32.0, epsilon = 1e-10); let norm = RobustArrayOps::robust_norm(&a).expect("operation should succeed");
523 let expected_norm = (1.0 + 4.0 + 9.0_f64).sqrt(); assert_relative_eq!(norm, expected_norm, epsilon = 1e-10);
525 }
526}