#![allow(deprecated)] #![allow(unused_imports)]
use approx::{assert_abs_diff_eq, assert_relative_eq};
use numrs2::prelude::*;
const SAMPLE_SIZE: usize = 100;
const TOLERANCE: f64 = 1e-8;
fn random_array_in_range(size: usize, min_value: f64, max_value: f64) -> Array<f64> {
let rng = random::default_rng();
let values = rng.random::<f64>(&[size]).unwrap();
values
.multiply_scalar(max_value - min_value)
.add_scalar(min_value)
}
fn arrays_approx_equal(a: &Array<f64>, b: &Array<f64>, tol: f64) -> bool {
if a.shape() != b.shape() {
return false;
}
let a_vec = a.to_vec();
let b_vec = b.to_vec();
for (a_val, b_val) in a_vec.iter().zip(b_vec.iter()) {
if (a_val - b_val).abs() > tol {
return false;
}
}
true
}
#[test]
fn test_erf_properties() {
let zero = Array::from_vec(vec![0.0]);
let erf_zero = erf(&zero);
assert_abs_diff_eq!(erf_zero.get(&[0]).unwrap(), 0.0, epsilon = TOLERANCE);
let x = random_array_in_range(SAMPLE_SIZE, 0.1, 5.0);
let minus_x = x.map(|v| -v);
let erf_x = erf(&x);
let erf_minus_x = erf(&minus_x);
let expected_erf_minus_x = erf_x.map(|v| -v);
assert!(
arrays_approx_equal(&erf_minus_x, &expected_erf_minus_x, TOLERANCE),
"erf(-x) should equal -erf(x)"
);
let large_x = random_array_in_range(SAMPLE_SIZE, -10.0, 10.0);
let erf_large_x = erf(&large_x);
for i in 0..SAMPLE_SIZE {
assert!(
erf_large_x.get(&[i]).unwrap().abs() <= 1.0,
"erf(x) should have magnitude <= 1"
);
}
let very_large_x = Array::from_vec(vec![10.0, 15.0, 20.0]);
let erf_very_large_x = erf(&very_large_x);
for i in 0..3 {
assert_abs_diff_eq!(erf_very_large_x.get(&[i]).unwrap(), 1.0, epsilon = 1e-6);
}
}
#[test]
fn test_erfc_properties() {
let zero = Array::from_vec(vec![0.0]);
let erfc_zero = erfc(&zero);
assert_abs_diff_eq!(erfc_zero.get(&[0]).unwrap(), 1.0, epsilon = TOLERANCE);
let x = random_array_in_range(SAMPLE_SIZE, -5.0, 5.0);
let erfc_x = erfc(&x);
let erf_x = erf(&x);
let one_minus_erf_x = erf_x.map(|v| 1.0 - v);
assert!(
arrays_approx_equal(&erfc_x, &one_minus_erf_x, TOLERANCE),
"erfc(x) should equal 1 - erf(x)"
);
let very_large_x = Array::from_vec(vec![10.0, 15.0, 20.0]);
let erfc_very_large_x = erfc(&very_large_x);
for i in 0..3 {
assert_abs_diff_eq!(erfc_very_large_x.get(&[i]).unwrap(), 0.0, epsilon = 1e-6);
}
let very_negative_x = Array::from_vec(vec![-10.0, -15.0, -20.0]);
let erfc_very_negative_x = erfc(&very_negative_x);
for i in 0..3 {
assert_abs_diff_eq!(erfc_very_negative_x.get(&[i]).unwrap(), 2.0, epsilon = 1e-6);
}
}
#[test]
fn test_erfinv_properties() {
let zero = Array::from_vec(vec![0.0]);
let erfinv_zero = erfinv(&zero);
assert_abs_diff_eq!(erfinv_zero.get(&[0]).unwrap(), 0.0, epsilon = TOLERANCE);
let x = random_array_in_range(SAMPLE_SIZE, 0.1, 0.9);
let minus_x = x.map(|v| -v);
let erfinv_x = erfinv(&x);
let erfinv_minus_x = erfinv(&minus_x);
let expected_erfinv_minus_x = erfinv_x.map(|v| -v);
assert!(
arrays_approx_equal(&erfinv_minus_x, &expected_erfinv_minus_x, TOLERANCE),
"erfinv(-x) should equal -erfinv(x)"
);
let y = random_array_in_range(SAMPLE_SIZE, -0.9, 0.9);
let erfinv_y = erfinv(&y);
let erf_erfinv_y = erf(&erfinv_y);
assert!(
arrays_approx_equal(&erf_erfinv_y, &y, 0.005),
"erf(erfinv(x)) should equal x"
);
}
#[test]
fn test_erfcinv_properties() {
let one = Array::from_vec(vec![1.0]);
let erfcinv_one = erfcinv(&one);
assert_abs_diff_eq!(erfcinv_one.get(&[0]).unwrap(), 0.0, epsilon = TOLERANCE);
let x = random_array_in_range(SAMPLE_SIZE, 0.1, 1.9);
let erfcinv_x = erfcinv(&x);
let one_minus_x = x.map(|v| 1.0 - v);
let erfinv_one_minus_x = erfinv(&one_minus_x);
assert!(
arrays_approx_equal(&erfcinv_x, &erfinv_one_minus_x, TOLERANCE),
"erfcinv(x) should equal erfinv(1 - x)"
);
let y = random_array_in_range(SAMPLE_SIZE, 0.1, 1.9);
let erfcinv_y = erfcinv(&y);
let erfc_erfcinv_y = erfc(&erfcinv_y);
assert!(
arrays_approx_equal(&erfc_erfcinv_y, &y, 0.01),
"erfc(erfcinv(x)) should equal x"
);
}
#[test]
fn test_gamma_properties() {
let integers = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let gamma_integers = gamma(&integers);
let expected = [1.0, 1.0, 2.0, 6.0, 24.0];
for (i, &expected_val) in expected.iter().enumerate() {
assert_abs_diff_eq!(
gamma_integers.get(&[i]).unwrap(),
expected_val,
epsilon = TOLERANCE
);
}
let x = random_array_in_range(SAMPLE_SIZE, 0.5, 10.0);
let x_plus_1 = x.add_scalar(1.0);
let gamma_x = gamma(&x);
let gamma_x_plus_1 = gamma(&x_plus_1);
let x_times_gamma_x = Array::from_vec(
x.to_vec()
.iter()
.zip(gamma_x.to_vec().iter())
.map(|(&x_val, &gamma_val)| x_val * gamma_val)
.collect(),
);
assert!(
arrays_approx_equal(&gamma_x_plus_1, &x_times_gamma_x, TOLERANCE * 10.0),
"gamma(x+1) should equal x * gamma(x)"
);
let half = Array::from_vec(vec![0.5]);
let gamma_half = gamma(&half);
let sqrt_pi = std::f64::consts::PI.sqrt();
assert_abs_diff_eq!(gamma_half.get(&[0]).unwrap(), sqrt_pi, epsilon = TOLERANCE);
}
#[test]
fn test_gammaln_properties() {
let x = random_array_in_range(SAMPLE_SIZE, 0.5, 10.0);
let gammaln_x = gammaln(&x);
let gamma_x = gamma(&x);
let ln_gamma_x = gamma_x.map(|v| v.ln());
assert!(
arrays_approx_equal(&gammaln_x, &ln_gamma_x, TOLERANCE),
"gammaln(x) should equal ln(gamma(x))"
);
let x = random_array_in_range(SAMPLE_SIZE, 0.5, 10.0);
let x_plus_1 = x.add_scalar(1.0);
let gammaln_x = gammaln(&x);
let gammaln_x_plus_1 = gammaln(&x_plus_1);
let ln_x_vec = x.map(|v| v.ln());
let ln_x_plus_gammaln_x = Array::from_vec(
ln_x_vec
.to_vec()
.iter()
.zip(gammaln_x.to_vec().iter())
.map(|(&ln_val, &gammaln_val)| ln_val + gammaln_val)
.collect(),
);
assert!(
arrays_approx_equal(&gammaln_x_plus_1, &ln_x_plus_gammaln_x, 0.1),
"gammaln(x+1) should equal ln(x) + gammaln(x)"
);
}
#[test]
fn test_digamma_properties() {
let x = random_array_in_range(SAMPLE_SIZE, 0.5, 10.0);
let x_plus_1 = x.add_scalar(1.0);
let digamma_x = digamma(&x);
let digamma_x_plus_1 = digamma(&x_plus_1);
let one_over_x = x.map(|v| 1.0 / v);
let expected = Array::from_vec(
digamma_x
.to_vec()
.iter()
.zip(one_over_x.to_vec().iter())
.map(|(&digamma_val, &one_over_val)| digamma_val + one_over_val)
.collect(),
);
assert!(
arrays_approx_equal(&digamma_x_plus_1, &expected, TOLERANCE * 10.0),
"digamma(x+1) should equal digamma(x) + 1/x"
);
let one = Array::from_vec(vec![1.0]);
let digamma_one = digamma(&one);
let euler_mascheroni = 0.577_215_664_901_532_9;
assert_abs_diff_eq!(
digamma_one.get(&[0]).unwrap(),
-euler_mascheroni,
epsilon = TOLERANCE
);
}
#[test]
fn test_bessel_properties() {
let zero = Array::from_vec(vec![0.0]);
let j0_at_zero = bessel_j(0, &zero);
assert_abs_diff_eq!(j0_at_zero.get(&[0]).unwrap(), 1.0, epsilon = TOLERANCE);
for n in 1..5 {
let jn_at_zero = bessel_j(n, &zero);
assert_abs_diff_eq!(jn_at_zero.get(&[0]).unwrap(), 0.0, epsilon = TOLERANCE);
}
let x = random_array_in_range(SAMPLE_SIZE, 0.1, 10.0);
for n in 1..5 {
let j_n = bessel_j(n, &x);
let j_minus_n = bessel_j(-n, &x);
let factor = if n % 2 == 0 { 1.0 } else { -1.0 };
let expected = j_n.multiply_scalar(factor);
assert!(
arrays_approx_equal(&j_minus_n, &expected, TOLERANCE * 10.0),
"J_{{-n}}(x) should equal (-1)^n * J_n(x)"
);
}
let zero = Array::from_vec(vec![0.0]);
let i0_at_zero = bessel_i(0, &zero);
assert_abs_diff_eq!(i0_at_zero.get(&[0]).unwrap(), 1.0, epsilon = TOLERANCE);
for n in 1..5 {
let in_at_zero = bessel_i(n, &zero);
assert_abs_diff_eq!(in_at_zero.get(&[0]).unwrap(), 0.0, epsilon = TOLERANCE);
}
let x = random_array_in_range(SAMPLE_SIZE, 0.1, 10.0);
for n in 0..5 {
let i_n = bessel_i(n, &x);
let i_minus_n = bessel_i(-n, &x);
assert!(
arrays_approx_equal(&i_minus_n, &i_n, TOLERANCE * 10.0),
"I_{{-n}}(x) should equal I_n(x)"
);
}
let x = random_array_in_range(SAMPLE_SIZE, 0.1, 10.0);
for n in 0..5 {
let k_n = bessel_k(n, &x);
let k_minus_n = bessel_k(-n, &x);
assert!(
arrays_approx_equal(&k_minus_n, &k_n, TOLERANCE * 10.0),
"K_{{-n}}(x) should equal K_n(x)"
);
}
}
#[test]
fn test_elliptic_integrals_properties() {
let zero = Array::from_vec(vec![0.0]);
let k_at_zero = ellipk(&zero);
let e_at_zero = ellipe(&zero);
let pi_half = std::f64::consts::PI / 2.0;
assert_abs_diff_eq!(k_at_zero.get(&[0]).unwrap(), pi_half, epsilon = TOLERANCE);
assert_abs_diff_eq!(e_at_zero.get(&[0]).unwrap(), pi_half, epsilon = TOLERANCE);
let one = Array::from_vec(vec![1.0]);
let e_at_one = ellipe(&one);
assert_abs_diff_eq!(e_at_one.get(&[0]).unwrap(), 1.0, epsilon = TOLERANCE);
let m_values = random_array_in_range(SAMPLE_SIZE, 0.0, 0.99);
let k_values = ellipk(&m_values);
for i in 0..SAMPLE_SIZE {
assert!(
k_values.get(&[i]).unwrap() >= pi_half,
"K(m) should be ≥ π/2 for 0 ≤ m < 1"
);
}
let m_values = random_array_in_range(SAMPLE_SIZE, 0.0, 0.99);
let e_values = ellipe(&m_values);
for i in 0..SAMPLE_SIZE {
assert!(
e_values.get(&[i]).unwrap() <= pi_half,
"E(m) should be ≤ π/2 for 0 ≤ m ≤ 1"
);
}
}
#[test]
fn test_compound_special_functions() {
let x = random_array_in_range(SAMPLE_SIZE, -2.0, 2.0);
let erf_x = erf(&x);
let erfinv_erf_x = erfinv(&erf_x);
assert!(
arrays_approx_equal(&erfinv_erf_x, &x, 0.2),
"erfinv(erf(x)) should equal x"
);
let x = random_array_in_range(SAMPLE_SIZE, 0.5, 10.0);
let gammaln_x = gammaln(&x);
let exp_gammaln_x = gammaln_x.map(|v| v.exp());
let gamma_x = gamma(&x);
assert!(
arrays_approx_equal(&exp_gammaln_x, &gamma_x, TOLERANCE * 10.0),
"exp(gammaln(x)) should equal gamma(x)"
);
let x = random_array_in_range(SAMPLE_SIZE, 1.0, 10.0);
let h = 1e-6;
let x_plus_h = x.add_scalar(h);
let x_minus_h = x.add_scalar(-h);
let gammaln_x_plus_h = gammaln(&x_plus_h);
let gammaln_x_minus_h = gammaln(&x_minus_h);
let difference = Array::from_vec(
gammaln_x_plus_h
.to_vec()
.iter()
.zip(gammaln_x_minus_h.to_vec().iter())
.map(|(&plus_h, &minus_h)| plus_h - minus_h)
.collect(),
);
let derivative_approx = difference.multiply_scalar(1.0 / (2.0 * h));
let digamma_x = digamma(&x);
assert!(
arrays_approx_equal(&derivative_approx, &digamma_x, 1e-4),
"digamma(x) should approximately equal the derivative of ln(gamma(x))"
);
}
#[test]
fn test_bessel_recurrence_relations() {
let x = random_array_in_range(SAMPLE_SIZE, 1.0, 10.0);
for n in 1..4 {
let j_n_minus_1 = bessel_j(n - 1, &x);
let j_n = bessel_j(n, &x);
let j_n_plus_1 = bessel_j(n + 1, &x);
let left_side = Array::from_vec(
j_n_minus_1
.to_vec()
.iter()
.zip(j_n_plus_1.to_vec().iter())
.map(|(&minus_1, &plus_1)| minus_1 + plus_1)
.collect(),
);
let factor_vec = x.map(|v| (2.0 * n as f64) / v);
let right_side = Array::from_vec(
factor_vec
.to_vec()
.iter()
.zip(j_n.to_vec().iter())
.map(|(&factor, &j_val)| factor * j_val)
.collect(),
);
assert!(
arrays_approx_equal(&left_side, &right_side, 1e-4),
"Bessel function recurrence relation should hold"
);
}
let x = random_array_in_range(SAMPLE_SIZE, 1.0, 10.0);
for n in 1..4 {
let i_n_minus_1 = bessel_i(n - 1, &x);
let i_n = bessel_i(n, &x);
let i_n_plus_1 = bessel_i(n + 1, &x);
let left_side = Array::from_vec(
i_n_minus_1
.to_vec()
.iter()
.zip(i_n_plus_1.to_vec().iter())
.map(|(&minus_1, &plus_1)| minus_1 - plus_1)
.collect(),
);
let factor_vec = x.map(|v| (2.0 * n as f64) / v);
let right_side = Array::from_vec(
factor_vec
.to_vec()
.iter()
.zip(i_n.to_vec().iter())
.map(|(&factor, &i_val)| factor * i_val)
.collect(),
);
assert!(
arrays_approx_equal(&left_side, &right_side, 1e-4),
"Modified Bessel function recurrence relation should hold"
);
}
}
#[test]
fn test_special_function_limits() {
let large_x = Array::from_vec(vec![5.0, 6.0, 7.0, 8.0]);
let erf_large_x = erf(&large_x);
for i in 0..4 {
assert!(
erf_large_x.get(&[i]).unwrap() > 0.99999,
"erf(x) should approach 1 for large x"
);
}
let erfc_large_x = erfc(&large_x);
for i in 0..4 {
assert!(
erfc_large_x.get(&[i]).unwrap() < 0.00001,
"erfc(x) should approach 0 for large x"
);
}
let x_values = Array::from_vec(vec![10.0, 11.0, 12.0, 13.0]);
let gamma_x = gamma(&x_values);
let factorial_values = [
362880.0, 3628800.0, 39916800.0, 479001600.0, ];
for (i, &expected) in factorial_values.iter().enumerate() {
assert_relative_eq!(
gamma_x.get(&[i]).unwrap(),
expected,
epsilon = expected * 1e-8
);
}
let large_x = Array::from_vec(vec![15.0, 20.0, 25.0]);
for n in 0..3 {
let k_n_large_x = bessel_k(n, &large_x);
for i in 0..3 {
assert!(
k_n_large_x.get(&[i]).unwrap() < 0.001,
"K_n(x) should approach 0 for large x"
);
}
}
}
#[test]
fn test_special_function_symmetry() {
let x = random_array_in_range(SAMPLE_SIZE, 0.1, 5.0);
let neg_x = x.map(|v| -v);
let erf_x = erf(&x);
let erf_neg_x = erf(&neg_x);
let neg_erf_x = erf_x.map(|v| -v);
assert!(
arrays_approx_equal(&erf_neg_x, &neg_erf_x, TOLERANCE),
"erf(-x) should equal -erf(x)"
);
let x = random_array_in_range(SAMPLE_SIZE, 0.5, 10.0);
let x_plus_1 = x.add_scalar(1.0);
let gamma_x = gamma(&x);
let gamma_x_plus_1 = gamma(&x_plus_1);
let x_gamma_x = Array::from_vec(
x.to_vec()
.iter()
.zip(gamma_x.to_vec().iter())
.map(|(&x_val, &gamma_val)| x_val * gamma_val)
.collect(),
);
assert!(
arrays_approx_equal(&gamma_x_plus_1, &x_gamma_x, TOLERANCE * 100.0),
"gamma(x+1) should equal x * gamma(x)"
);
let x = random_array_in_range(10, 0.5, 5.0);
for n in 0..5 {
let j_n = bessel_j(n, &x);
let j_neg_n = bessel_j(-n, &x);
if n % 2 == 0 {
assert!(
arrays_approx_equal(&j_neg_n, &j_n, TOLERANCE * 10.0),
"J_(-n)(x) should equal J_n(x) for even n"
);
} else {
let neg_j_n = j_n.map(|v| -v);
assert!(
arrays_approx_equal(&j_neg_n, &neg_j_n, TOLERANCE * 10.0),
"J_(-n)(x) should equal -J_n(x) for odd n"
);
}
}
}
#[test]
fn test_special_functions_with_vector_args() {
let a = random_array_in_range(SAMPLE_SIZE, 0.5, 5.0);
let x = random_array_in_range(SAMPLE_SIZE, 0.1, 10.0);
let a_ones = Array::<f64>::full(&[SAMPLE_SIZE], 1.0);
let gammainc_ones = gammainc(&a_ones, &x).expect("Failed to compute gammainc");
let expected = x.map(|v| 1.0 - (-v).exp());
assert!(
arrays_approx_equal(&gammainc_ones, &expected, 0.1),
"gammainc(1, x) should equal 1 - exp(-x)"
);
let gammainc_result = gammainc(&a, &x).expect("Failed to compute gammainc");
for i in 0..SAMPLE_SIZE {
let val = gammainc_result.get(&[i]).unwrap();
assert!(
(0.0..=1.0).contains(&val),
"gammainc values should be between 0 and 1"
);
}
let a_fixed = Array::<f64>::full(&[5], 2.0);
let x_increasing = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let gammainc_increasing =
gammainc(&a_fixed, &x_increasing).expect("Failed to compute gammainc");
let gammainc_vec = gammainc_increasing.to_vec();
for i in 1..5 {
assert!(
gammainc_vec[i] > gammainc_vec[i - 1],
"gammainc should be increasing with x for fixed a"
);
}
}