#![cfg(feature = "scirs")]
use approx::assert_relative_eq;
use numrs2::array::Array;
use numrs2::interop::scirs_compat::*;
use numrs2::random::advanced_distributions::maxwell;
use numrs2::random::distributions::{multivariate_normal_with_rotation, set_seed, vonmises};
use numrs2::random::distributions_enhanced::truncated_normal;
use std::f64::consts::PI;
fn calculate_mean(data: &[f64]) -> f64 {
data.iter().sum::<f64>() / data.len() as f64
}
fn calculate_variance(data: &[f64]) -> f64 {
let mean = calculate_mean(data);
let sum_squared_diff = data.iter().map(|&x| (x - mean).powi(2)).sum::<f64>();
sum_squared_diff / (data.len() - 1) as f64
}
#[test]
fn test_noncentral_chisquare_statistics() {
set_seed(12345);
let df = 5.0;
let nonc = 2.0;
let samples = noncentral_chisquare(df, nonc, &[10000]).unwrap();
let data = samples.to_vec();
let mean = calculate_mean(&data);
let variance = calculate_variance(&data);
let expected_mean = df + nonc;
let expected_variance = 2.0 * df + 4.0 * nonc;
assert_relative_eq!(mean, expected_mean, epsilon = 0.1, max_relative = 0.05);
assert_relative_eq!(
variance,
expected_variance,
epsilon = 0.5,
max_relative = 0.1
);
}
#[test]
fn test_noncentral_f_statistics() {
set_seed(12345);
let dfnum = 10.0;
let dfden = 20.0;
let nonc = 2.0;
let samples = noncentral_f(dfnum, dfden, nonc, &[5000]).unwrap();
let data = samples.to_vec();
let mean = calculate_mean(&data);
let expected_mean = (dfden / (dfden - 2.0)) * (dfnum + nonc) / dfnum;
assert_relative_eq!(mean, expected_mean, epsilon = 0.15, max_relative = 0.1);
}
#[test]
fn test_vonmises_concentration() {
set_seed(12345);
let mu: f64 = 0.0;
let kappas: [f64; 3] = [0.5, 2.0, 10.0];
let sample_count = 5000;
for &kappa in &kappas {
let samples = vonmises(mu, kappa, &[sample_count]).unwrap();
let data = samples.to_vec();
for &val in &data {
assert!(
(-PI..=PI).contains(&val),
"Sample {} outside valid range [-π, π]",
val
);
}
let mean_sin = data.iter().map(|&x| x.sin()).sum::<f64>() / sample_count as f64;
let mean_cos = data.iter().map(|&x| x.cos()).sum::<f64>() / sample_count as f64;
let circular_mean = mean_sin.atan2(mean_cos);
assert!(
(circular_mean - mu).abs() < 0.1,
"Circular mean {:.4} too far from expected {:.4} for kappa = {}",
circular_mean,
mu,
kappa
);
let within_narrow = data
.iter()
.filter(|&&x| (x - mu).abs() <= (PI / 12.0))
.count();
let within_wide = data
.iter()
.filter(|&&x| (x - mu).abs() <= (PI / 4.0))
.count();
let narrow_proportion = within_narrow as f64 / sample_count as f64;
let wide_proportion = within_wide as f64 / sample_count as f64;
assert!(
narrow_proportion <= wide_proportion,
"Narrow range should have fewer samples than wide range"
);
assert!(
wide_proportion > 0.0,
"Should have some samples in wide range"
);
println!(
"kappa = {:.1}: {:.1}% within π/12, {:.1}% within π/4",
kappa,
narrow_proportion * 100.0,
wide_proportion * 100.0
);
if kappa >= 2.0 {
assert!(
narrow_proportion > 0.1,
"High kappa should have reasonable concentration in narrow range"
);
}
}
}
#[test]
fn test_maxwell_statistics() {
set_seed(12345);
let scales = [0.5, 1.0, 2.0];
let sample_count = 5000;
for &scale in &scales {
let samples = maxwell(scale, &[sample_count]).unwrap();
let data = samples.to_vec();
let mean = calculate_mean(&data);
let variance = calculate_variance(&data);
let expected_mean = 2.0 * scale * (2.0 / PI).sqrt();
let expected_variance = scale.powi(2) * (3.0 * PI - 8.0) / PI;
assert_relative_eq!(mean, expected_mean, epsilon = 0.05, max_relative = 0.05);
assert_relative_eq!(
variance,
expected_variance,
epsilon = 0.1,
max_relative = 0.1
);
}
}
#[test]
fn test_truncated_normal_statistics() {
set_seed(12345);
let test_cases = [
(0.0, 1.0, -1.0, 1.0),
(0.0, 1.0, 0.0, 2.0),
(0.0, 1.0, -2.0, 0.0),
(1.0, 2.0, -1.0, 3.0),
];
for &(mean, std, low, high) in &test_cases {
let samples = truncated_normal(mean, std, low, high, &[5000]).unwrap();
let data = samples.to_vec();
for &val in &data {
assert!(
val >= low && val <= high,
"Sample {} outside bounds [{}, {}]",
val,
low,
high
);
}
let actual_mean = calculate_mean(&data);
let alpha = (low - mean) / std;
let beta = (high - mean) / std;
let phi_alpha = (-0.5f64 * alpha * alpha).exp() / (2.0f64 * PI).sqrt();
let phi_beta = (-0.5f64 * beta * beta).exp() / (2.0f64 * PI).sqrt();
let phi_cdf_alpha = 0.5 * (1.0 + erf(alpha / 2.0f64.sqrt()));
let phi_cdf_beta = 0.5 * (1.0 + erf(beta / 2.0f64.sqrt()));
let expected_mean = mean + std * (phi_alpha - phi_beta) / (phi_cdf_beta - phi_cdf_alpha);
if (expected_mean - actual_mean).abs() < 0.1
|| (expected_mean - actual_mean).abs() / expected_mean.max(1.0) < 0.1
{
} else {
println!(
"Warning: Mean discrepancy for case ({}, {}, {}, {}): expected {:.4}, got {:.4}",
mean, std, low, high, expected_mean, actual_mean
);
assert!(
(expected_mean - actual_mean).abs() < 0.2,
"Mean too far from expected: expected {:.4}, got {:.4}",
expected_mean,
actual_mean
);
}
}
}
#[test]
fn test_multivariate_normal_rotation() {
set_seed(12345);
let mean = vec![0.0, 0.0];
let corr = 0.7; let cov_data = vec![1.0, corr, corr, 1.0];
let cov = Array::from_vec(cov_data).reshape(&[2, 2]);
let samples1 = multivariate_normal_with_rotation(&mean, &cov, Some(&[5000]), None).unwrap();
let data1 = samples1.to_vec();
let mut sum_x = 0.0;
let mut sum_y = 0.0;
let mut sum_xy = 0.0;
let mut sum_x2 = 0.0;
let mut sum_y2 = 0.0;
for i in 0..5000 {
let x = data1[i * 2];
let y = data1[i * 2 + 1];
sum_x += x;
sum_y += y;
sum_xy += x * y;
sum_x2 += x * x;
sum_y2 += y * y;
}
let n = 5000.0;
let mean_x = sum_x / n;
let mean_y = sum_y / n;
let cov_xy = (sum_xy - n * mean_x * mean_y) / (n - 1.0);
let var_x: f64 = (sum_x2 - n * mean_x * mean_x) / (n - 1.0);
let var_y: f64 = (sum_y2 - n * mean_y * mean_y) / (n - 1.0);
let corr_xy = cov_xy / (var_x.sqrt() * var_y.sqrt());
assert_relative_eq!(corr_xy, corr, epsilon = 0.05);
let rot_data = vec![0.0, 1.0, -1.0, 0.0];
let rotation = Array::from_vec(rot_data).reshape(&[2, 2]);
let samples2 =
multivariate_normal_with_rotation(&mean, &cov, Some(&[5000]), Some(&rotation)).unwrap();
let data2 = samples2.to_vec();
let mut sum_x = 0.0;
let mut sum_y = 0.0;
let mut sum_xy = 0.0;
let mut sum_x2 = 0.0;
let mut sum_y2 = 0.0;
for i in 0..5000 {
let x = data2[i * 2];
let y = data2[i * 2 + 1];
sum_x += x;
sum_y += y;
sum_xy += x * y;
sum_x2 += x * x;
sum_y2 += y * y;
}
let mean_x = sum_x / n;
let mean_y = sum_y / n;
let cov_xy = (sum_xy - n * mean_x * mean_y) / (n - 1.0);
let var_x: f64 = (sum_x2 - n * mean_x * mean_x) / (n - 1.0);
let var_y: f64 = (sum_y2 - n * mean_y * mean_y) / (n - 1.0);
let corr_xy_rotated = cov_xy / (var_x.sqrt() * var_y.sqrt());
assert!((corr_xy_rotated + 1.0).abs() < 0.1 || (corr_xy_rotated + corr).abs() < 0.1);
}
#[allow(dead_code)]
fn erf(x: f64) -> f64 {
let a1 = 0.254829592;
let a2 = -0.284496736;
let a3 = 1.421413741;
let a4 = -1.453152027;
let a5 = 1.061405429;
let p = 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
}