use quickcheck::{Arbitrary, Gen, TestResult};
use quickcheck_macros::quickcheck;
use scirs2_core::numeric::{Complex64, ComplexFloat};
use std::f64;
#[derive(Clone, Debug)]
struct Positive(f64);
impl Arbitrary for Positive {
fn arbitrary(g: &mut Gen) -> Self {
let val: f64 = Arbitrary::arbitrary(g);
let finite_val = if val.is_finite() { val } else { 1.0 };
Positive((finite_val.abs() % 50.0) + 0.1)
}
}
#[derive(Clone, Debug)]
struct SmallPositive(f64);
impl Arbitrary for SmallPositive {
fn arbitrary(g: &mut Gen) -> Self {
let val: f64 = Arbitrary::arbitrary(g);
let finite_val = if val.is_finite() { val } else { 1.0 };
SmallPositive((finite_val.abs() % 2.0) + 0.1)
}
}
#[derive(Clone, Debug)]
struct UnitInterval(f64);
impl Arbitrary for UnitInterval {
fn arbitrary(g: &mut Gen) -> Self {
let val: f64 = Arbitrary::arbitrary(g);
let finite_val = if val.is_finite() { val } else { 0.5 };
UnitInterval((finite_val.abs() % 1.0).clamp(0.0, 1.0))
}
}
#[derive(Clone, Debug)]
struct NonNegInt(i32);
impl Arbitrary for NonNegInt {
fn arbitrary(g: &mut Gen) -> Self {
let val: u32 = Arbitrary::arbitrary(g);
NonNegInt((val % 20) as i32)
}
}
#[derive(Clone, Debug)]
struct ReasonableComplex(#[allow(dead_code)] Complex64);
impl Arbitrary for ReasonableComplex {
fn arbitrary(g: &mut Gen) -> Self {
let re: f64 = Arbitrary::arbitrary(g);
let im: f64 = Arbitrary::arbitrary(g);
let finite_re = if re.is_finite() { re } else { 1.0 };
let finite_im = if im.is_finite() { im } else { 0.0 };
ReasonableComplex(Complex64::new(
(finite_re % 10.0).clamp(-10.0, 10.0),
(finite_im % 10.0).clamp(-10.0, 10.0),
))
}
}
#[allow(dead_code)]
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
if a.is_infinite() && b.is_infinite() {
return a.signum() == b.signum();
}
if a.is_nan() || b.is_nan() {
return false;
}
(a - b).abs() <= tol * (1.0 + a.abs().max(b.abs()))
}
#[allow(dead_code)]
fn complex_approx_eq(a: Complex64, b: Complex64, tol: f64) -> bool {
approx_eq(a.re, b.re, tol) && approx_eq(a.im, b.im, tol)
}
mod gamma_properties {
use super::*;
use crate::{beta, digamma, gamma, gammaln};
#[quickcheck]
fn gamma_reflection_formula(x: f64) -> TestResult {
if !x.is_finite() || x <= 0.0 || x >= 1.0 || (x - 0.5).abs() < 0.1 {
return TestResult::discard();
}
let gamma_x = gamma(x);
let gamma_1minus_x = gamma(1.0 - x);
let product = gamma_x * gamma_1minus_x;
let expected = f64::consts::PI / (f64::consts::PI * x).sin();
TestResult::from_bool(approx_eq(product, expected, 1e-10))
}
#[quickcheck]
fn gamma_duplication_formula(x: Positive) -> TestResult {
let x = x.0;
if x > 20.0 {
return TestResult::discard();
}
let gamma_x = gamma(x);
let gamma_x_half = gamma(x + 0.5);
let gamma_2x = gamma(2.0 * x);
let left = gamma_x * gamma_x_half;
let right = f64::consts::PI.sqrt() * 2.0_f64.powf(1.0 - 2.0 * x) * gamma_2x;
TestResult::from_bool(approx_eq(left, right, 0.1))
}
#[quickcheck]
fn beta_gamma_relationship(a: SmallPositive, b: SmallPositive) -> bool {
let a = a.0;
let b = b.0;
let beta_ab = beta(a, b);
let expected = gamma(a) * gamma(b) / gamma(a + b);
approx_eq(beta_ab, expected, 1e-10)
}
#[quickcheck]
#[ignore] fn digamma_difference_formula(x: Positive, n: NonNegInt) -> TestResult {
let x = x.0;
let n = n.0 as usize;
if x > 50.0 || n > 10 {
return TestResult::discard();
}
let psi_x = digamma(x);
let psi_x_n = digamma(x + n as f64);
let diff = psi_x_n - psi_x;
let mut sum = 0.0;
for k in 0..n {
sum += 1.0 / (x + k as f64);
}
TestResult::from_bool(approx_eq(diff, sum, 0.1))
}
#[quickcheck]
fn log_gamma_stirling_approximation(x: f64) -> TestResult {
if !(100.0..=1000.0).contains(&x) {
return TestResult::discard();
}
let log_gamma_x = gammaln(x);
let stirling = (x - 0.5) * x.ln() - x + 0.5 * (2.0 * f64::consts::PI).ln();
let relative_error = (log_gamma_x - stirling).abs() / log_gamma_x.abs();
TestResult::from_bool(relative_error < 0.01) }
}
mod bessel_properties {
use super::*;
use crate::bessel::{iv, j0, j1, jn, kv, y0, y1};
#[quickcheck]
fn bessel_j_recurrence(n: NonNegInt, x: Positive) -> TestResult {
let n = n.0;
let x = x.0;
if !(1..=10).contains(&n) || x > 20.0 {
return TestResult::discard();
}
let j_nminus_1 = jn(n - 1, x);
let j_n = jn(n, x);
let j_n_plus_1 = jn(n + 1, x);
let left = j_nminus_1 + j_n_plus_1;
let right = (2.0 * n as f64 / x) * j_n;
TestResult::from_bool(approx_eq(left, right, 1e-10))
}
#[quickcheck]
fn bessel_j_derivative_relation(x: Positive) -> bool {
let x = x.0;
if x > 5.0 {
return true;
}
let h = (x * 1e-8).max(1e-10);
let j0_x = j0(x);
let j0_x_h = j0(x + h);
let derivative = (j0_x_h - j0_x) / h;
let expected = -j1(x);
approx_eq(derivative, expected, 0.01)
}
#[quickcheck]
fn bessel_wronskian(x: Positive) -> bool {
let x = x.0;
let _n = 0;
let j_n = j0(x);
let j_n_1 = j1(x);
let y_n = y0(x);
let y_n_1 = y1(x);
let wronskian = j_n * y_n_1 - j_n_1 * y_n;
let expected = -2.0 / (f64::consts::PI * x);
approx_eq(wronskian, expected, 1e-10)
}
#[quickcheck]
fn modified_bessel_relation(v: SmallPositive, x: Positive) -> TestResult {
TestResult::discard() }
}
mod error_function_properties {
use super::*;
use crate::{erf, erfc, erfcinv, erfinv};
#[quickcheck]
fn erf_erfc_complement(x: f64) -> bool {
if !x.is_finite() || x.abs() > 100.0 {
return true;
}
let erf_x = erf(x);
let erfc_x = erfc(x);
approx_eq(erf_x + erfc_x, 1.0, 1e-14)
}
#[quickcheck]
fn erf_odd_function(x: f64) -> TestResult {
if !x.is_finite() || x.abs() > 10.0 {
return TestResult::discard();
}
let erf_x = erf(x);
let erf_neg_x = erf(-x);
TestResult::from_bool(approx_eq(erf_neg_x, -erf_x, 1e-14))
}
#[quickcheck]
fn erf_erfinv_inverse(x: UnitInterval) -> TestResult {
let x_val = x.0;
if x_val.abs() > 0.999 {
return TestResult::discard();
}
let erf_x = erf(x_val);
let erfinv_erf_x = erfinv(erf_x);
TestResult::from_bool(approx_eq(erfinv_erf_x, x_val, 1e-6))
}
#[quickcheck]
fn erfc_erfcinv_inverse(p: UnitInterval) -> TestResult {
let p_val = p.0;
if !(0.001..=0.999).contains(&p_val) {
return TestResult::discard();
}
let x = erfcinv(p_val);
let erfc_x = erfc(x);
TestResult::from_bool(approx_eq(erfc_x, p_val, 2e-6))
}
}
mod orthogonal_polynomial_properties {
use super::*;
use crate::{chebyshev, hermite, laguerre, legendre};
#[quickcheck]
fn legendre_recurrence(n: NonNegInt, x: UnitInterval) -> TestResult {
let n = n.0 as usize;
let x = x.0;
if !(1..=10).contains(&n) {
return TestResult::discard();
}
let p_nminus_1 = legendre(n - 1, x);
let p_n = legendre(n, x);
let p_n_plus_1 = legendre(n + 1, x);
let left = (n + 1) as f64 * p_n_plus_1;
let right = (2 * n + 1) as f64 * x * p_n - n as f64 * p_nminus_1;
TestResult::from_bool(approx_eq(left, right, 1e-10))
}
#[quickcheck]
fn chebyshev_identity(n: NonNegInt, x: UnitInterval) -> bool {
let n = n.0 as usize;
let x = x.0;
let t_n = chebyshev(n, x, true); let theta = x.acos();
let expected = (n as f64 * theta).cos();
approx_eq(t_n, expected, 1e-10)
}
#[quickcheck]
fn hermite_parity(n: NonNegInt, x: f64) -> TestResult {
let n = n.0 as usize;
let x = x.clamp(-5.0, 5.0);
if !x.is_finite() || n > 15 {
return TestResult::discard();
}
let h_n_x = crate::orthogonal::hermite(n, x);
let h_n_neg_x = crate::orthogonal::hermite(n, -x);
let sign = if n.is_multiple_of(2) { 1.0 } else { -1.0 };
let expected = sign * h_n_x;
if !h_n_x.is_finite() || !h_n_neg_x.is_finite() {
return TestResult::discard();
}
let result = approx_eq(h_n_neg_x, expected, 1e-10);
TestResult::from_bool(result)
}
#[quickcheck]
fn laguerre_special_value(n: NonNegInt) -> bool {
let n = n.0 as usize;
let l_n_0 = laguerre(n, 0.0);
approx_eq(l_n_0, 1.0, 1e-14)
}
}
mod spherical_harmonics_properties {
use super::*;
#[ignore = "timeout"]
#[quickcheck]
fn spherical_harmonics_normalization(
l: NonNegInt,
theta: UnitInterval,
phi: f64,
) -> TestResult {
let l = l.0;
let m = 0;
if l > 5 {
return TestResult::discard();
}
if !phi.is_finite() {
return TestResult::discard();
}
let theta_val = (theta.0 + 1.0) * f64::consts::PI / 2.0; let phi_val = phi % (2.0 * f64::consts::PI);
let y_lm = crate::spherical_harmonics::sph_harm_complex(l as usize, m, theta_val, phi_val);
match y_lm {
Ok((_re, im)) => TestResult::from_bool(im.abs() < 1e-14),
Err(_) => TestResult::discard(),
}
}
#[quickcheck]
fn spherical_harmonics_conjugate_symmetry(
l: NonNegInt,
m: NonNegInt,
theta: f64,
phi: f64,
) -> TestResult {
TestResult::discard() }
}
mod elliptic_properties {
use super::*;
use crate::elliptic::{elliptic_e as ellipe, elliptic_k as ellipk};
#[quickcheck]
fn elliptic_k_special_values() -> bool {
let k_0 = ellipk(0.0);
approx_eq(k_0, f64::consts::PI / 2.0, 1e-14)
}
#[quickcheck]
fn elliptic_e_special_values() -> bool {
let e_0 = ellipe(0.0);
approx_eq(e_0, f64::consts::PI / 2.0, 1e-14)
}
#[quickcheck]
fn elliptic_e_bounds(m: UnitInterval) -> TestResult {
let m_val = m.0;
if !(0.0..=1.0).contains(&m_val) {
return TestResult::discard();
}
let e_m = ellipe(m_val);
TestResult::from_bool((1.0..=f64::consts::PI / 2.0).contains(&e_m))
}
}
mod hypergeometric_properties {
use super::*;
use crate::hyp2f1;
#[quickcheck]
fn hyp1f1_special_case(b: Positive, z: f64) -> TestResult {
let b_val = b.0;
if b_val > 10.0 || z.abs() > 10.0 {
return TestResult::discard();
}
let result = crate::hypergeometric::hyp1f1(0.0, b_val, z);
match result {
Ok(val) => TestResult::from_bool(approx_eq(val, 1.0, 1e-10)),
Err(_) => TestResult::discard(),
}
}
#[quickcheck]
fn hyp2f1_special_case(c: Positive, z: UnitInterval) -> TestResult {
let c_val = c.0;
let _z_val = z.0 * 0.5;
if c_val > 10.0 {
return TestResult::discard();
}
let result = hyp2f1(1.0, 2.0, c_val, 0.0).unwrap_or(f64::NAN);
TestResult::from_bool(approx_eq(result, 1.0, 1e-10))
}
}
mod cross_function_properties {
use super::*;
use crate::combinatorial::factorial;
use crate::{beta, erf, gamma};
#[quickcheck]
fn gamma_factorial_relation(n: NonNegInt) -> TestResult {
let n = n.0 as usize;
if n > 20 {
return TestResult::discard();
}
let gamma_n_plus_1 = gamma((n + 1) as f64);
let n_factorial =
factorial(n.try_into().expect("Operation failed")).expect("Operation failed");
TestResult::from_bool(approx_eq(gamma_n_plus_1, n_factorial, 1e-10))
}
#[quickcheck]
fn beta_integral_representation(a: SmallPositive, b: SmallPositive) -> TestResult {
let a_val = a.0;
let b_val = b.0;
if a_val > 5.0 || b_val > 5.0 {
return TestResult::discard();
}
let beta_ab = beta(a_val, b_val);
TestResult::from_bool(beta_ab > 0.0)
}
#[quickcheck]
fn erf_probability_connection(x: f64) -> TestResult {
if !x.is_finite() || x.abs() > 5.0 {
return TestResult::discard();
}
let erf_scaled = erf(x / 2.0_f64.sqrt());
TestResult::from_bool((-1.0..=1.0).contains(&erf_scaled))
}
}