use approx::assert_relative_eq;
use ndarray::{Array1, Array2};
use ngboost_rs::dist::{
Cauchy, CauchyFixedVar, Distribution, DistributionMethods, Exponential,
Gamma, HalfNormal, Laplace, LogNormal, Normal, NormalFixedMean, NormalFixedVar, Poisson,
StudentT, TFixedDf, TFixedDfFixedVar, Weibull,
};
#[test]
fn test_normal_methods_basic() {
let params =
Array2::from_shape_vec((3, 2), vec![0.0, 0.0, 1.0, 0.0, -1.0, 2.0_f64.ln()]).unwrap();
let dist = Normal::from_params(¶ms);
let mean = dist.mean();
assert_relative_eq!(mean[0], 0.0, epsilon = 1e-10);
assert_relative_eq!(mean[1], 1.0, epsilon = 1e-10);
assert_relative_eq!(mean[2], -1.0, epsilon = 1e-10);
let var = dist.variance();
assert_relative_eq!(var[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(var[1], 1.0, epsilon = 1e-10);
assert_relative_eq!(var[2], 4.0, epsilon = 1e-10);
let mode = dist.mode();
assert_relative_eq!(mode[0], mean[0], epsilon = 1e-10);
}
#[test]
fn test_normal_cdf_ppf_roundtrip() {
let params = Array2::from_shape_vec((2, 2), vec![0.0, 0.0, 5.0, 1.0_f64.ln()]).unwrap();
let dist = Normal::from_params(¶ms);
let y = Array1::from_vec(vec![0.5, 6.0]);
let cdf_y = dist.cdf(&y);
let ppf_cdf_y = dist.ppf(&cdf_y);
assert_relative_eq!(ppf_cdf_y[0], y[0], epsilon = 1e-6);
assert_relative_eq!(ppf_cdf_y[1], y[1], epsilon = 1e-6);
let q = Array1::from_vec(vec![0.25, 0.75]);
let ppf_q = dist.ppf(&q);
let cdf_ppf_q = dist.cdf(&ppf_q);
assert_relative_eq!(cdf_ppf_q[0], q[0], epsilon = 1e-6);
assert_relative_eq!(cdf_ppf_q[1], q[1], epsilon = 1e-6);
}
#[test]
fn test_normal_sample_statistics() {
let params = Array2::from_shape_vec((1, 2), vec![10.0, 0.5_f64.ln()]).unwrap();
let dist = Normal::from_params(¶ms);
let samples = dist.sample(10000);
assert_eq!(samples.shape(), &[10000, 1]);
let sample_mean: f64 = samples.column(0).mean().unwrap();
let sample_var: f64 = samples.column(0).var(0.0);
assert!((sample_mean - 10.0).abs() < 0.1);
assert!((sample_var - 0.25).abs() < 0.05);
}
#[test]
fn test_normal_pdf_integrates() {
let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
let dist = Normal::from_params(¶ms);
let y = Array1::from_vec(vec![0.0]);
let pdf = dist.pdf(&y);
assert_relative_eq!(
pdf[0],
1.0 / (2.0 * std::f64::consts::PI).sqrt(),
epsilon = 1e-10
);
}
#[test]
fn test_normal_interval() {
let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
let dist = Normal::from_params(¶ms);
let (lower, upper) = dist.interval(0.05);
assert_relative_eq!(lower[0], -1.96, epsilon = 0.01);
assert_relative_eq!(upper[0], 1.96, epsilon = 0.01);
}
#[test]
fn test_lognormal_methods_basic() {
let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
let dist = LogNormal::from_params(¶ms);
let mean = dist.mean();
assert_relative_eq!(mean[0], (0.5_f64).exp(), epsilon = 1e-10);
let median = dist.median();
assert_relative_eq!(median[0], 1.0, epsilon = 1e-10);
let mode = dist.mode();
assert_relative_eq!(mode[0], (-1.0_f64).exp(), epsilon = 1e-10);
}
#[test]
fn test_lognormal_samples_positive() {
let params = Array2::from_shape_vec((1, 2), vec![1.0, 0.5_f64.ln()]).unwrap();
let dist = LogNormal::from_params(¶ms);
let samples = dist.sample(1000);
assert!(samples.iter().all(|&x| x > 0.0));
}
#[test]
fn test_exponential_methods_basic() {
let params = Array2::from_shape_vec((1, 1), vec![2.0_f64.ln()]).unwrap();
let dist = Exponential::from_params(¶ms);
let mean = dist.mean();
assert_relative_eq!(mean[0], 2.0, epsilon = 1e-10);
let var = dist.variance();
assert_relative_eq!(var[0], 4.0, epsilon = 1e-10);
let mode = dist.mode();
assert_relative_eq!(mode[0], 0.0, epsilon = 1e-10);
let median = dist.median();
assert_relative_eq!(median[0], std::f64::consts::LN_2 * 2.0, epsilon = 1e-10);
}
#[test]
fn test_exponential_memoryless_property() {
let params = Array2::from_shape_vec((1, 1), vec![0.0]).unwrap();
let dist = Exponential::from_params(¶ms);
let s = 1.0;
let t = 0.5;
let sf_s = dist.sf(&Array1::from_vec(vec![s]))[0];
let sf_s_plus_t = dist.sf(&Array1::from_vec(vec![s + t]))[0];
let sf_t = dist.sf(&Array1::from_vec(vec![t]))[0];
assert_relative_eq!(sf_s_plus_t / sf_s, sf_t, epsilon = 1e-10);
}
#[test]
fn test_gamma_methods_basic() {
let params = Array2::from_shape_vec((1, 2), vec![2.0_f64.ln(), 0.0]).unwrap();
let dist = Gamma::from_params(¶ms);
let mean = dist.mean();
assert_relative_eq!(mean[0], 2.0, epsilon = 1e-10);
let var = dist.variance();
assert_relative_eq!(var[0], 2.0, epsilon = 1e-10);
let mode = dist.mode();
assert_relative_eq!(mode[0], 1.0, epsilon = 1e-10);
}
#[test]
fn test_gamma_samples_positive() {
let params = Array2::from_shape_vec((1, 2), vec![2.0_f64.ln(), 0.5_f64.ln()]).unwrap();
let dist = Gamma::from_params(¶ms);
let samples = dist.sample(1000);
assert!(samples.iter().all(|&x| x >= 0.0));
}
#[test]
fn test_laplace_methods_basic() {
let params = Array2::from_shape_vec((1, 2), vec![5.0, 0.0]).unwrap();
let dist = Laplace::from_params(¶ms);
let mean = dist.mean();
assert_relative_eq!(mean[0], 5.0, epsilon = 1e-10);
let var = dist.variance();
assert_relative_eq!(var[0], 2.0, epsilon = 1e-10);
assert_relative_eq!(dist.median()[0], 5.0, epsilon = 1e-10);
assert_relative_eq!(dist.mode()[0], 5.0, epsilon = 1e-10);
}
#[test]
fn test_laplace_symmetry() {
let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
let dist = Laplace::from_params(¶ms);
let y_pos = Array1::from_vec(vec![1.0]);
let y_neg = Array1::from_vec(vec![-1.0]);
assert_relative_eq!(dist.pdf(&y_pos)[0], dist.pdf(&y_neg)[0], epsilon = 1e-10);
assert_relative_eq!(dist.cdf(&y_neg)[0], dist.sf(&y_pos)[0], epsilon = 1e-10);
}
#[test]
fn test_cauchy_undefined_moments() {
let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
let dist = Cauchy::from_params(¶ms);
assert!(dist.mean()[0].is_nan());
assert!(dist.variance()[0].is_nan());
assert_relative_eq!(dist.median()[0], 0.0, epsilon = 1e-10);
assert_relative_eq!(dist.mode()[0], 0.0, epsilon = 1e-10);
}
#[test]
fn test_cauchy_heavy_tails() {
let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
let dist = Cauchy::from_params(¶ms);
let sf_10 = dist.sf(&Array1::from_vec(vec![10.0]))[0];
assert!(sf_10 > 0.01); }
#[test]
fn test_weibull_methods_basic() {
let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
let dist = Weibull::from_params(¶ms);
let mean = dist.mean();
assert_relative_eq!(mean[0], 1.0, epsilon = 1e-6);
let mode = dist.mode();
assert_relative_eq!(mode[0], 0.0, epsilon = 1e-10);
}
#[test]
fn test_weibull_rayleigh_case() {
let params = Array2::from_shape_vec((1, 2), vec![2.0_f64.ln(), 0.0]).unwrap();
let dist = Weibull::from_params(¶ms);
let mode = dist.mode();
assert_relative_eq!(mode[0], 0.5_f64.sqrt(), epsilon = 1e-6);
}
#[test]
fn test_halfnormal_methods_basic() {
let params = Array2::from_shape_vec((1, 1), vec![0.0]).unwrap();
let dist = HalfNormal::from_params(¶ms);
let expected_mean = (2.0 / std::f64::consts::PI).sqrt();
assert_relative_eq!(dist.mean()[0], expected_mean, epsilon = 1e-10);
assert_relative_eq!(dist.mode()[0], 0.0, epsilon = 1e-10);
}
#[test]
fn test_halfnormal_samples_positive() {
let params = Array2::from_shape_vec((1, 1), vec![1.0_f64.ln()]).unwrap();
let dist = HalfNormal::from_params(¶ms);
let samples = dist.sample(1000);
assert!(samples.iter().all(|&x| x >= 0.0));
}
#[test]
fn test_poisson_methods_basic() {
let params = Array2::from_shape_vec((2, 1), vec![1.0_f64.ln(), 5.0_f64.ln()]).unwrap();
let dist = Poisson::from_params(¶ms);
assert_relative_eq!(dist.mean()[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(dist.variance()[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(dist.mean()[1], 5.0, epsilon = 1e-10);
assert_relative_eq!(dist.variance()[1], 5.0, epsilon = 1e-10);
assert_relative_eq!(dist.mode()[0], 1.0, epsilon = 1e-10);
assert!(dist.mode()[1] == 4.0 || dist.mode()[1] == 5.0);
}
#[test]
fn test_poisson_samples_integers() {
let params = Array2::from_shape_vec((1, 1), vec![3.0_f64.ln()]).unwrap();
let dist = Poisson::from_params(¶ms);
let samples = dist.sample(1000);
assert!(samples.iter().all(|&x| x >= 0.0 && x.fract() == 0.0));
}
#[test]
fn test_studentt_methods_basic() {
let params = Array2::from_shape_vec((1, 3), vec![0.0, 0.0, 5.0_f64.ln()]).unwrap();
let dist = StudentT::from_params(¶ms);
assert_relative_eq!(dist.mean()[0], 0.0, epsilon = 1e-10);
let expected_var = 5.0 / 3.0;
assert_relative_eq!(dist.variance()[0], expected_var, epsilon = 1e-10);
assert_relative_eq!(dist.mode()[0], 0.0, epsilon = 1e-10);
assert_relative_eq!(dist.median()[0], 0.0, epsilon = 1e-10);
}
#[test]
fn test_studentt_approaches_normal() {
let params_t = Array2::from_shape_vec((1, 3), vec![0.0, 0.0, 100.0_f64.ln()]).unwrap();
let dist_t = StudentT::from_params(¶ms_t);
let params_n = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
let dist_n = Normal::from_params(¶ms_n);
let y = Array1::from_vec(vec![0.0]);
let pdf_t = dist_t.pdf(&y)[0];
let pdf_n = dist_n.pdf(&y)[0];
assert!((pdf_t - pdf_n).abs() < 0.01);
let y = Array1::from_vec(vec![1.0]);
let cdf_t = dist_t.cdf(&y)[0];
let cdf_n = dist_n.cdf(&y)[0];
assert!((cdf_t - cdf_n).abs() < 0.01);
}
#[test]
fn test_normal_fixed_var() {
let params = Array2::from_shape_vec((1, 1), vec![5.0]).unwrap();
let dist = NormalFixedVar::from_params(¶ms);
assert_relative_eq!(dist.mean()[0], 5.0, epsilon = 1e-10);
assert_relative_eq!(dist.variance()[0], 1.0, epsilon = 1e-10);
}
#[test]
fn test_normal_fixed_mean() {
let params = Array2::from_shape_vec((1, 1), vec![1.0]).unwrap();
let dist = NormalFixedMean::from_params(¶ms);
assert_relative_eq!(dist.mean()[0], 0.0, epsilon = 1e-10);
assert_relative_eq!(dist.std()[0], std::f64::consts::E, epsilon = 1e-10);
}
#[test]
fn test_tfixeddf() {
let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
let dist = TFixedDf::from_params(¶ms);
assert_relative_eq!(dist.mean()[0], 0.0, epsilon = 1e-10);
assert_relative_eq!(dist.variance()[0], 3.0, epsilon = 1e-10);
}
#[test]
fn test_tfixeddfvar() {
let params = Array2::from_shape_vec((1, 1), vec![2.0]).unwrap();
let dist = TFixedDfFixedVar::from_params(¶ms);
assert_relative_eq!(dist.mean()[0], 2.0, epsilon = 1e-10);
assert_relative_eq!(dist.median()[0], 2.0, epsilon = 1e-10);
}
#[test]
fn test_cauchy_fixed_var() {
let params = Array2::from_shape_vec((1, 1), vec![3.0]).unwrap();
let dist = CauchyFixedVar::from_params(¶ms);
assert!(dist.mean()[0].is_nan());
assert_relative_eq!(dist.median()[0], 3.0, epsilon = 1e-10);
}
#[test]
fn test_cdf_sf_sum_to_one() {
let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
let normal = Normal::from_params(¶ms);
let laplace = Laplace::from_params(¶ms);
let cauchy = Cauchy::from_params(¶ms);
let y = Array1::from_vec(vec![0.5]);
assert_relative_eq!(normal.cdf(&y)[0] + normal.sf(&y)[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(laplace.cdf(&y)[0] + laplace.sf(&y)[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(cauchy.cdf(&y)[0] + cauchy.sf(&y)[0], 1.0, epsilon = 1e-10);
}
#[test]
fn test_interval_contains_median() {
let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
let normal = Normal::from_params(¶ms);
let laplace = Laplace::from_params(¶ms);
for alpha in [0.1, 0.2, 0.3, 0.4] {
let (lower, upper) = normal.interval(alpha);
let median = normal.median()[0];
assert!(lower[0] <= median && median <= upper[0]);
let (lower, upper) = laplace.interval(alpha);
let median = laplace.median()[0];
assert!(lower[0] <= median && median <= upper[0]);
}
}
#[test]
fn test_pdf_logpdf_consistency() {
let params = Array2::from_shape_vec((1, 2), vec![1.0, 0.5_f64.ln()]).unwrap();
let normal = Normal::from_params(¶ms);
let y = Array1::from_vec(vec![0.5]);
let pdf = normal.pdf(&y)[0];
let logpdf = normal.logpdf(&y)[0];
assert_relative_eq!(pdf.ln(), logpdf, epsilon = 1e-10);
}
#[test]
fn test_samples_have_correct_mean_variance() {
let n_samples = 10000;
let tolerance = 0.1;
let params = Array2::from_shape_vec((1, 2), vec![5.0, 1.0_f64.ln()]).unwrap();
let normal = Normal::from_params(¶ms);
let samples = normal.sample(n_samples);
let sample_mean = samples.column(0).mean().unwrap();
let sample_var = samples.column(0).var(0.0);
let expected_mean = normal.mean()[0];
let expected_var = normal.variance()[0];
assert!(
(sample_mean - expected_mean).abs() / expected_mean.abs().max(1.0) < tolerance,
"Normal sample mean {} differs from expected {}",
sample_mean,
expected_mean
);
assert!(
(sample_var - expected_var).abs() / expected_var < tolerance,
"Normal sample variance {} differs from expected {}",
sample_var,
expected_var
);
let params = Array2::from_shape_vec((1, 1), vec![2.0_f64.ln()]).unwrap();
let exp = Exponential::from_params(¶ms);
let samples = exp.sample(n_samples);
let sample_mean = samples.column(0).mean().unwrap();
let expected_mean = exp.mean()[0];
assert!(
(sample_mean - expected_mean).abs() / expected_mean < tolerance,
"Exponential sample mean {} differs from expected {}",
sample_mean,
expected_mean
);
}
#[test]
fn test_cdf_at_extremes() {
let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
let normal = Normal::from_params(¶ms);
let y = Array1::from_vec(vec![-10.0]);
assert!(normal.cdf(&y)[0] < 0.001);
let y = Array1::from_vec(vec![10.0]);
assert!(normal.cdf(&y)[0] > 0.999);
}
#[test]
fn test_ppf_at_boundaries() {
let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
let normal = Normal::from_params(¶ms);
let q = Array1::from_vec(vec![0.5]);
assert_relative_eq!(normal.ppf(&q)[0], normal.median()[0], epsilon = 1e-10);
let q_low = Array1::from_vec(vec![0.001]);
let q_high = Array1::from_vec(vec![0.999]);
assert!(normal.ppf(&q_low)[0].is_finite());
assert!(normal.ppf(&q_high)[0].is_finite());
}
#[test]
fn test_multiple_observations() {
let params = Array2::from_shape_vec(
(5, 2),
vec![
0.0,
0.0,
1.0,
0.0,
2.0,
0.0,
-1.0,
0.5_f64.ln(),
0.0,
1.0_f64.ln(),
],
)
.unwrap();
let normal = Normal::from_params(¶ms);
let mean = normal.mean();
assert_eq!(mean.len(), 5);
assert_relative_eq!(mean[0], 0.0, epsilon = 1e-10);
assert_relative_eq!(mean[1], 1.0, epsilon = 1e-10);
assert_relative_eq!(mean[2], 2.0, epsilon = 1e-10);
assert_relative_eq!(mean[3], -1.0, epsilon = 1e-10);
assert_relative_eq!(mean[4], 0.0, epsilon = 1e-10);
let samples = normal.sample(100);
assert_eq!(samples.shape(), &[100, 5]);
}