use scirs2_stats::distributions::validation::{check_cdf, check_pdf};
use scirs2_stats::distributions::{
Bernoulli, Beta, Binomial, Cauchy, ChiSquare, Exponential, Gamma, Geometric, Hypergeometric,
Laplace, Lognormal, NegativeBinomial, Normal, Pareto, Poisson, StudentT, Uniform, Weibull,
F as FDist,
};
#[test]
fn test_normal_standard_reference() {
let dist = Normal::new(0.0_f64, 1.0).expect("valid params");
let pdf0 = dist.pdf(0.0);
assert!(
check_pdf(pdf0, 0.3989422804014327, 1e-9, "Normal(0,1)", 0.0),
"Normal(0,1) pdf(0) = {pdf0}"
);
let pdf1 = dist.pdf(1.0);
assert!(
check_pdf(pdf1, 0.24197072451914337, 1e-9, "Normal(0,1)", 1.0),
"Normal(0,1) pdf(1) = {pdf1}"
);
let cdf0 = dist.cdf(0.0);
assert!(
check_cdf(cdf0, 0.5, 1e-9, "Normal(0,1)", 0.0),
"Normal(0,1) cdf(0) = {cdf0}"
);
let cdf_196 = dist.cdf(1.96);
assert!(
check_cdf(cdf_196, 0.9750021048517796, 1e-6, "Normal(0,1)", 1.96),
"Normal(0,1) cdf(1.96) = {cdf_196}"
);
let q975 = dist.ppf(0.975).expect("valid p");
assert!(
(q975 - 1.959963984540054).abs() < 5e-9,
"Normal(0,1) ppf(0.975) = {q975}"
);
let q025 = dist.ppf(0.025).expect("valid p");
assert!(
(q025 - (-1.959963984540054)).abs() < 5e-9,
"Normal(0,1) ppf(0.025) = {q025}"
);
for &p in &[0.025_f64, 0.5, 0.975] {
let q = dist.ppf(p).expect("valid p");
let roundtrip = dist.cdf(q);
assert!(
(roundtrip - p).abs() < 1e-6,
"Normal(0,1) ppf round-trip at p={p}: got {roundtrip}"
);
}
}
#[test]
fn test_normal_shifted_reference() {
let dist = Normal::new(1.0_f64, 2.0).expect("valid params");
let pdf1 = dist.pdf(1.0);
assert!(
check_pdf(pdf1, 0.19947114020071635, 1e-9, "Normal(1,2)", 1.0),
"Normal(1,2) pdf(1) = {pdf1}"
);
let cdf3 = dist.cdf(3.0);
assert!(
check_cdf(cdf3, 0.8413447460685429, 1e-6, "Normal(1,2)", 3.0),
"Normal(1,2) cdf(3) = {cdf3}"
);
for &p in &[0.1_f64, 0.5, 0.9] {
let q = dist.ppf(p).expect("valid p");
let roundtrip = dist.cdf(q);
assert!(
(roundtrip - p).abs() < 1e-6,
"Normal(1,2) ppf round-trip at p={p}: got {roundtrip}"
);
}
}
#[test]
fn test_student_t_df5_reference() {
let dist = StudentT::new(5.0_f64, 0.0, 1.0).expect("valid params");
let pdf0 = dist.pdf(0.0);
assert!(
pdf0 > 0.378 && pdf0 < 0.381,
"StudentT(5) pdf(0) out of expected range [0.378, 0.381]: got {pdf0}"
);
let pdf1 = dist.pdf(1.0);
assert!(
pdf1 > 0.21 && pdf1 < 0.23,
"StudentT(5) pdf(1) out of expected range [0.21, 0.23]: got {pdf1}"
);
let cdf0 = dist.cdf(0.0);
assert!(
check_cdf(cdf0, 0.5, 1e-9, "StudentT(5,0,1)", 0.0),
"StudentT(5) cdf(0) = {cdf0}"
);
let cdf2 = dist.cdf(2.0);
assert!(
check_cdf(cdf2, 0.9490715680859902, 1e-4, "StudentT(5,0,1)", 2.0),
"StudentT(5) cdf(2) = {cdf2}"
);
}
#[test]
fn test_chi_square_df3_reference() {
let dist = ChiSquare::new(3.0_f64, 0.0, 1.0).expect("valid params");
let pdf1 = dist.pdf(1.0);
assert!(
check_pdf(pdf1, 0.24197072451914337, 1e-6, "ChiSquare(3)", 1.0),
"ChiSquare(3) pdf(1) = {pdf1}"
);
let pdf3 = dist.pdf(3.0);
assert!(
check_pdf(pdf3, 0.15418033659602215, 1e-6, "ChiSquare(3)", 3.0),
"ChiSquare(3) pdf(3) = {pdf3}"
);
let cdf1 = dist.cdf(1.0);
let cdf3 = dist.cdf(3.0);
let cdf5 = dist.cdf(5.0);
assert!(
(0.0..=1.0).contains(&cdf1),
"ChiSquare(3) cdf(1) in [0,1]: got {cdf1}"
);
assert!(
cdf3 >= cdf1,
"ChiSquare(3) CDF non-decreasing at 1->3: {cdf1} -> {cdf3}"
);
assert!(
cdf5 >= cdf3,
"ChiSquare(3) CDF non-decreasing at 3->5: {cdf3} -> {cdf5}"
);
}
#[test]
fn test_chi_square_df2_reference() {
let dist = ChiSquare::new(2.0_f64, 0.0, 1.0).expect("valid params");
let pdf1 = dist.pdf(1.0);
assert!(
check_pdf(pdf1, 0.30326532985631666, 1e-6, "ChiSquare(2)", 1.0),
"ChiSquare(2) pdf(1) = {pdf1}"
);
let cdf2 = dist.cdf(2.0);
assert!(
check_cdf(cdf2, 0.6321205588285578, 1e-9, "ChiSquare(2)", 2.0),
"ChiSquare(2) cdf(2) = {cdf2}"
);
let cdf4 = dist.cdf(4.0);
assert!(
check_cdf(cdf4, 0.8646647167633873, 1e-9, "ChiSquare(2)", 4.0),
"ChiSquare(2) cdf(4) = {cdf4}"
);
let cdf_half = dist.cdf(0.5);
assert!(
check_cdf(cdf_half, 0.22119921692859512, 1e-9, "ChiSquare(2)", 0.5),
"ChiSquare(2) cdf(0.5) = {cdf_half}"
);
}
#[test]
fn test_exponential_rate1_reference() {
let dist = Exponential::new(1.0_f64, 0.0).expect("valid params");
let pdf1 = dist.pdf(1.0);
assert!(
check_pdf(pdf1, 0.36787944117144233, 1e-9, "Exponential(λ=1)", 1.0),
"Exp(λ=1) pdf(1) = {pdf1}"
);
let cdf1 = dist.cdf(1.0);
assert!(
check_cdf(cdf1, 0.6321205588285578, 1e-9, "Exponential(λ=1)", 1.0),
"Exp(λ=1) cdf(1) = {cdf1}"
);
let cdf0 = dist.cdf(0.0);
assert!(
check_cdf(cdf0, 0.0, 1e-9, "Exponential(λ=1)", 0.0),
"Exp(λ=1) cdf(0) = {cdf0}"
);
for &p in &[0.1_f64, 0.5, 0.9] {
let q = dist.ppf(p).expect("valid p");
let roundtrip = dist.cdf(q);
assert!(
(roundtrip - p).abs() < 1e-9,
"Exp(λ=1) ppf round-trip at p={p}: got {roundtrip}"
);
}
}
#[test]
fn test_exponential_rate2_reference() {
let dist = Exponential::new(2.0_f64, 0.0).expect("valid params");
let pdf_half = dist.pdf(0.5);
assert!(
check_pdf(pdf_half, 0.7357588823428847, 1e-9, "Exponential(λ=2)", 0.5),
"Exp(λ=2) pdf(0.5) = {pdf_half}"
);
let cdf_half = dist.cdf(0.5);
assert!(
check_cdf(cdf_half, 0.6321205588285578, 1e-9, "Exponential(λ=2)", 0.5),
"Exp(λ=2) cdf(0.5) = {cdf_half}"
);
}
#[test]
fn test_gamma_alpha2_beta1_reference() {
let dist = Gamma::new(2.0_f64, 1.0, 0.0).expect("valid params");
let pdf1 = dist.pdf(1.0);
assert!(
check_pdf(pdf1, 0.36787944117144233, 1e-9, "Gamma(2,1)", 1.0),
"Gamma(2,1) pdf(1) = {pdf1}"
);
let cdf2 = dist.cdf(2.0);
assert!(
check_cdf(cdf2, 0.5939941502901619, 1e-5, "Gamma(2,1)", 2.0),
"Gamma(2,1) cdf(2) = {cdf2}"
);
let q50 = dist.ppf(0.5).expect("valid p");
assert!(
q50 > 0.5 && q50 < 5.0,
"Gamma(2,1) ppf(0.5) sanity: got {q50}"
);
}
#[test]
fn test_gamma_alpha3_beta2_reference() {
let dist = Gamma::new(3.0_f64, 2.0, 0.0).expect("valid params");
let pdf2 = dist.pdf(2.0);
assert!(
check_pdf(pdf2, 0.09196986029286058, 1e-6, "Gamma(3,2)", 2.0),
"Gamma(3,2) pdf(2) = {pdf2}"
);
let cdf6 = dist.cdf(6.0);
assert!(
cdf6 > 0.0 && cdf6 < 1.0,
"Gamma(3,2) cdf(6) in (0,1): got {cdf6}"
);
}
#[test]
fn test_beta_alpha2_beta5_reference() {
let dist = Beta::new(2.0_f64, 5.0, 0.0, 1.0).expect("valid params");
let pdf_03 = dist.pdf(0.3);
assert!(
check_pdf(pdf_03, 2.160_9, 1e-5, "Beta(2,5)", 0.3),
"Beta(2,5) pdf(0.3) = {pdf_03}"
);
let pdf_02 = dist.pdf(0.2);
assert!(
check_pdf(pdf_02, 2.4576, 1e-6, "Beta(2,5)", 0.2),
"Beta(2,5) pdf(0.2) = {pdf_02}"
);
let cdf_02 = dist.cdf(0.2);
assert!(
check_cdf(cdf_02, 0.34464, 1e-6, "Beta(2,5)", 0.2),
"Beta(2,5) cdf(0.2) = {cdf_02}"
);
let cdf_half = dist.cdf(0.5);
assert!(
check_cdf(cdf_half, 0.890625, 1e-6, "Beta(2,5)", 0.5),
"Beta(2,5) cdf(0.5) = {cdf_half}"
);
}
#[test]
fn test_beta_symmetric_reference() {
let dist = Beta::new(2.0_f64, 2.0, 0.0, 1.0).expect("valid params");
let cdf_half = dist.cdf(0.5);
assert!(
check_cdf(cdf_half, 0.5, 1e-9, "Beta(2,2)", 0.5),
"Beta(2,2) cdf(0.5) = {cdf_half}"
);
}
#[test]
fn test_uniform_standard_reference() {
let dist = Uniform::new(0.0_f64, 1.0).expect("valid params");
let pdf_half = dist.pdf(0.5);
assert!(
check_pdf(pdf_half, 1.0, 1e-9, "Uniform(0,1)", 0.5),
"Uniform(0,1) pdf(0.5) = {pdf_half}"
);
let cdf_half = dist.cdf(0.5);
assert!(
check_cdf(cdf_half, 0.5, 1e-9, "Uniform(0,1)", 0.5),
"Uniform(0,1) cdf(0.5) = {cdf_half}"
);
let cdf0 = dist.cdf(0.0);
assert!(
check_cdf(cdf0, 0.0, 1e-9, "Uniform(0,1)", 0.0),
"Uniform(0,1) cdf(0) = {cdf0}"
);
let cdf1 = dist.cdf(1.0);
assert!(
check_cdf(cdf1, 1.0, 1e-9, "Uniform(0,1)", 1.0),
"Uniform(0,1) cdf(1) = {cdf1}"
);
for &p in &[0.1_f64, 0.5, 0.9] {
let q = dist.ppf(p).expect("valid p");
let roundtrip = dist.cdf(q);
assert!(
(roundtrip - p).abs() < 1e-9,
"Uniform(0,1) ppf round-trip at p={p}: got {roundtrip}"
);
}
}
#[test]
fn test_uniform_shifted_reference() {
let dist = Uniform::new(1.0_f64, 3.0).expect("valid params");
let pdf2 = dist.pdf(2.0);
assert!(
check_pdf(pdf2, 0.5, 1e-9, "Uniform(1,3)", 2.0),
"Uniform(1,3) pdf(2) = {pdf2}"
);
let cdf2 = dist.cdf(2.0);
assert!(
check_cdf(cdf2, 0.5, 1e-9, "Uniform(1,3)", 2.0),
"Uniform(1,3) cdf(2) = {cdf2}"
);
}
#[test]
fn test_cauchy_standard_reference() {
let dist = Cauchy::new(0.0_f64, 1.0).expect("valid params");
let pdf0 = dist.pdf(0.0);
assert!(
check_pdf(pdf0, std::f64::consts::FRAC_1_PI, 1e-9, "Cauchy(0,1)", 0.0),
"Cauchy(0,1) pdf(0) = {pdf0}"
);
let cdf0 = dist.cdf(0.0);
assert!(
check_cdf(cdf0, 0.5, 1e-9, "Cauchy(0,1)", 0.0),
"Cauchy(0,1) cdf(0) = {cdf0}"
);
let pdf1 = dist.pdf(1.0);
assert!(
check_pdf(pdf1, 0.15915494309189535, 1e-9, "Cauchy(0,1)", 1.0),
"Cauchy(0,1) pdf(1) = {pdf1}"
);
for &p in &[0.25_f64, 0.5, 0.75] {
let q = dist.ppf(p).expect("valid p");
let roundtrip = dist.cdf(q);
assert!(
(roundtrip - p).abs() < 1e-9,
"Cauchy(0,1) ppf round-trip at p={p}: got {roundtrip}"
);
}
}
#[test]
fn test_laplace_standard_reference() {
let dist = Laplace::new(0.0_f64, 1.0).expect("valid params");
let pdf0 = dist.pdf(0.0);
assert!(
check_pdf(pdf0, 0.5, 1e-9, "Laplace(0,1)", 0.0),
"Laplace(0,1) pdf(0) = {pdf0}"
);
let cdf0 = dist.cdf(0.0);
assert!(
check_cdf(cdf0, 0.5, 1e-9, "Laplace(0,1)", 0.0),
"Laplace(0,1) cdf(0) = {cdf0}"
);
let cdf1 = dist.cdf(1.0);
assert!(
check_cdf(cdf1, 0.8160602794142788, 1e-9, "Laplace(0,1)", 1.0),
"Laplace(0,1) cdf(1) = {cdf1}"
);
for &p in &[0.1_f64, 0.5, 0.9] {
let q = dist.ppf(p).expect("valid p");
let roundtrip = dist.cdf(q);
assert!(
(roundtrip - p).abs() < 1e-9,
"Laplace(0,1) ppf round-trip at p={p}: got {roundtrip}"
);
}
}
#[test]
fn test_lognormal_standard_reference() {
let dist = Lognormal::new(0.0_f64, 1.0, 0.0).expect("valid params");
let pdf1 = dist.pdf(1.0);
assert!(
check_pdf(pdf1, 0.3989422804014327, 1e-9, "Lognormal(0,1)", 1.0),
"Lognormal(0,1) pdf(1) = {pdf1}"
);
let cdf1 = dist.cdf(1.0);
assert!(
check_cdf(cdf1, 0.5, 1e-9, "Lognormal(0,1)", 1.0),
"Lognormal(0,1) cdf(1) = {cdf1}"
);
for &p in &[0.1_f64, 0.5, 0.9] {
let q = dist.ppf(p).expect("valid p");
let roundtrip = dist.cdf(q);
assert!(
(roundtrip - p).abs() < 1e-6,
"Lognormal(0,1) ppf round-trip at p={p}: got {roundtrip}"
);
}
}
#[test]
fn test_pareto_alpha3_reference() {
let dist = Pareto::new(3.0_f64, 1.0, 0.0).expect("valid params");
let pdf_at_scale = dist.pdf(1.0);
assert!(
(pdf_at_scale - 3.0).abs() < 1e-10,
"Pareto(3,1) pdf(scale=1) = {pdf_at_scale}, expected 3.0"
);
let pdf2 = dist.pdf(2.0);
assert!(
check_pdf(pdf2, 0.1875, 1e-9, "Pareto(3,1)", 2.0),
"Pareto(3,1) pdf(2) = {pdf2}"
);
let pdf3 = dist.pdf(3.0);
assert!(
check_pdf(pdf3, 3.0 / 81.0, 1e-9, "Pareto(3,1)", 3.0),
"Pareto(3,1) pdf(3) = {pdf3}"
);
let cdf2 = dist.cdf(2.0);
assert!(
check_cdf(cdf2, 0.875, 1e-9, "Pareto(3,1)", 2.0),
"Pareto(3,1) cdf(2) = {cdf2}"
);
let cdf_at_scale = dist.cdf(1.0);
assert_eq!(cdf_at_scale, 0.0, "Pareto(3,1) cdf at scale boundary = 0");
for &p in &[0.1_f64, 0.5, 0.9] {
let q = dist.ppf(p).expect("valid p");
let roundtrip = dist.cdf(q);
assert!(
(roundtrip - p).abs() < 1e-9,
"Pareto(3,1) ppf round-trip at p={p}: got {roundtrip}"
);
}
}
#[test]
fn test_weibull_k2_reference() {
let dist = Weibull::new(2.0_f64, 1.0, 0.0).expect("valid params");
let pdf1 = dist.pdf(1.0);
assert!(
check_pdf(pdf1, 0.7357588823428847, 1e-9, "Weibull(2,1)", 1.0),
"Weibull(2,1) pdf(1) = {pdf1}"
);
let cdf1 = dist.cdf(1.0);
assert!(
check_cdf(cdf1, 0.6321205588285578, 1e-9, "Weibull(2,1)", 1.0),
"Weibull(2,1) cdf(1) = {cdf1}"
);
for &p in &[0.1_f64, 0.5, 0.9] {
let q = dist.ppf(p).expect("valid p");
let roundtrip = dist.cdf(q);
assert!(
(roundtrip - p).abs() < 1e-9,
"Weibull(2,1) ppf round-trip at p={p}: got {roundtrip}"
);
}
}
#[test]
fn test_weibull_k1_is_exponential_reference() {
let dist = Weibull::new(1.0_f64, 1.0, 0.0).expect("valid params");
let pdf1 = dist.pdf(1.0);
assert!(
check_pdf(pdf1, 0.36787944117144233, 1e-9, "Weibull(1,1)", 1.0),
"Weibull(1,1)=Exp(1) pdf(1) = {pdf1}"
);
let cdf1 = dist.cdf(1.0);
assert!(
check_cdf(cdf1, 0.6321205588285578, 1e-9, "Weibull(1,1)", 1.0),
"Weibull(1,1)=Exp(1) cdf(1) = {cdf1}"
);
}
#[test]
fn test_f_distribution_reference() {
let dist = FDist::new(5.0_f64, 10.0, 0.0, 1.0).expect("valid params");
let cdf1 = dist.cdf(1.0);
assert!(
check_cdf(cdf1, 0.5348805734622, 1e-6, "F(5,10)", 1.0),
"F(5,10) cdf(1) = {cdf1}"
);
let cdf_half = dist.cdf(0.5);
assert!(
cdf_half < cdf1,
"F(5,10) CDF non-decreasing at 0.5->1: {cdf_half} -> {cdf1}"
);
let pdf1 = dist.pdf(1.0);
assert!(
check_pdf(pdf1, 0.4954797834866, 1e-6, "F(5,10)", 1.0),
"F(5,10) pdf(1) = {pdf1}"
);
}
#[test]
fn test_f_distribution_d1_2_d2_10_reference() {
let dist = FDist::new(2.0_f64, 10.0, 0.0, 1.0).expect("valid params");
let cdf1 = dist.cdf(1.0);
assert!(
check_cdf(cdf1, 0.5981224279835416, 1e-6, "F(2,10)", 1.0),
"F(2,10) cdf(1) = {cdf1}"
);
let pdf1 = dist.pdf(1.0);
assert!(
check_pdf(pdf1, 0.3348979766803841, 1e-6, "F(2,10)", 1.0),
"F(2,10) pdf(1) = {pdf1}"
);
}
#[test]
fn test_poisson_mu3_reference() {
let dist = Poisson::new(3.0_f64, 0.0).expect("valid params");
let pmf3 = dist.pmf(3.0);
assert!(
check_pdf(pmf3, 0.22404180765538775, 1e-9, "Poisson(3)", 3.0),
"Poisson(3) pmf(3) = {pmf3}"
);
let pmf0 = dist.pmf(0.0);
assert!(
check_pdf(pmf0, 0.04978706836786395, 1e-9, "Poisson(3)", 0.0),
"Poisson(3) pmf(0) = {pmf0}"
);
let cdf5 = dist.cdf(5.0);
assert!(
check_cdf(cdf5, 0.9160820579686966, 1e-6, "Poisson(3)", 5.0),
"Poisson(3) cdf(5) = {cdf5}"
);
}
#[test]
fn test_poisson_mu1_reference() {
let dist = Poisson::new(1.0_f64, 0.0).expect("valid params");
let pmf0 = dist.pmf(0.0);
assert!(
check_pdf(pmf0, 0.36787944117144233, 1e-9, "Poisson(1)", 0.0),
"Poisson(1) pmf(0) = {pmf0}"
);
let pmf1 = dist.pmf(1.0);
assert!(
check_pdf(pmf1, 0.36787944117144233, 1e-9, "Poisson(1)", 1.0),
"Poisson(1) pmf(1) = {pmf1}"
);
let cdf0 = dist.cdf(0.0);
assert!(
check_cdf(cdf0, 0.36787944117144233, 1e-6, "Poisson(1)", 0.0),
"Poisson(1) cdf(0) = {cdf0}"
);
}
#[test]
fn test_binomial_n10_p05_reference() {
let dist = Binomial::new(10, 0.5_f64).expect("valid params");
let pmf5 = dist.pmf(5.0);
assert!(
check_pdf(pmf5, 0.24609375, 1e-9, "Binomial(10,0.5)", 5.0),
"Binomial(10,0.5) pmf(5) = {pmf5}"
);
let pmf0 = dist.pmf(0.0);
assert!(
check_pdf(pmf0, 0.0009765625, 1e-9, "Binomial(10,0.5)", 0.0),
"Binomial(10,0.5) pmf(0) = {pmf0}"
);
let cdf7 = dist.cdf(7.0);
assert!(
check_cdf(cdf7, 0.9453125, 1e-6, "Binomial(10,0.5)", 7.0),
"Binomial(10,0.5) cdf(7) = {cdf7}"
);
for &p in &[0.1_f64, 0.5, 0.9] {
let q = dist.ppf(p).expect("valid p");
let roundtrip = dist.cdf(q);
assert!(
roundtrip >= p - 1e-9,
"Binomial(10,0.5) ppf round-trip at p={p}: cdf(ppf(p))={roundtrip} < p"
);
}
}
#[test]
fn test_binomial_n20_p03_reference() {
let dist = Binomial::new(20, 0.3_f64).expect("valid params");
let pmf6 = dist.pmf(6.0);
assert!(
pmf6 > 0.0 && pmf6 < 0.5,
"Binomial(20,0.3) pmf(6) sanity: {pmf6}"
);
let cdf10 = dist.cdf(10.0);
assert!(cdf10 > 0.9, "Binomial(20,0.3) cdf(10) > 0.9: got {cdf10}");
}
#[test]
fn test_bernoulli_p03_reference() {
let dist = Bernoulli::new(0.3_f64).expect("valid params");
let pmf0 = dist.pmf(0.0);
assert!(
check_pdf(pmf0, 0.7, 1e-9, "Bernoulli(0.3)", 0.0),
"Bernoulli(0.3) pmf(0) = {pmf0}"
);
let pmf1 = dist.pmf(1.0);
assert!(
check_pdf(pmf1, 0.3, 1e-9, "Bernoulli(0.3)", 1.0),
"Bernoulli(0.3) pmf(1) = {pmf1}"
);
let pmf2 = dist.pmf(2.0);
assert_eq!(pmf2, 0.0, "Bernoulli(0.3) pmf(2) must be 0");
}
#[test]
fn test_bernoulli_p05_reference() {
let dist = Bernoulli::new(0.5_f64).expect("valid params");
let pmf0 = dist.pmf(0.0);
let pmf1 = dist.pmf(1.0);
assert!(
check_pdf(pmf0, 0.5, 1e-9, "Bernoulli(0.5)", 0.0),
"Bernoulli(0.5) pmf(0) = {pmf0}"
);
assert!(
check_pdf(pmf1, 0.5, 1e-9, "Bernoulli(0.5)", 1.0),
"Bernoulli(0.5) pmf(1) = {pmf1}"
);
}
#[test]
fn test_geometric_p05_reference() {
let dist = Geometric::new(0.5_f64).expect("valid params");
let pmf0 = dist.pmf(0.0);
assert!(
check_pdf(pmf0, 0.5, 1e-9, "Geometric(0.5)", 0.0),
"Geometric(0.5) pmf(0) = {pmf0}"
);
let pmf1 = dist.pmf(1.0);
assert!(
check_pdf(pmf1, 0.25, 1e-9, "Geometric(0.5)", 1.0),
"Geometric(0.5) pmf(1) = {pmf1}"
);
let pmf2 = dist.pmf(2.0);
assert!(
check_pdf(pmf2, 0.125, 1e-9, "Geometric(0.5)", 2.0),
"Geometric(0.5) pmf(2) = {pmf2}"
);
}
#[test]
fn test_geometric_p03_reference() {
let dist = Geometric::new(0.3_f64).expect("valid params");
let pmf0 = dist.pmf(0.0);
assert!(
check_pdf(pmf0, 0.3, 1e-9, "Geometric(0.3)", 0.0),
"Geometric(0.3) pmf(0) = {pmf0}"
);
let pmf1 = dist.pmf(1.0);
assert!(
check_pdf(pmf1, 0.21, 1e-9, "Geometric(0.3)", 1.0),
"Geometric(0.3) pmf(1) = {pmf1}"
);
let pmf2 = dist.pmf(2.0);
assert!(
check_pdf(pmf2, 0.147, 1e-9, "Geometric(0.3)", 2.0),
"Geometric(0.3) pmf(2) = {pmf2}"
);
}
#[test]
fn test_negative_binomial_reference() {
let dist = NegativeBinomial::new(5.0_f64, 0.3).expect("valid params");
let pmf0 = dist.pmf(0.0);
assert!(
check_pdf(pmf0, 0.00243, 1e-7, "NegBinom(5,0.3)", 0.0),
"NegBinom(5,0.3) pmf(0) = {pmf0}"
);
let pmf3 = dist.pmf(3.0);
assert!(
pmf3 > 0.0 && pmf3 < 1.0,
"NegBinom(5,0.3) pmf(3) in (0,1): got {pmf3}"
);
let total: f64 = (0..=50).map(|k| dist.pmf(k as f64)).sum();
assert!(
(total - 1.0).abs() < 1e-4,
"NegBinom(5,0.3) pmf sum ≈ 1: got {total}"
);
}
#[test]
fn test_negative_binomial_r1_is_geometric_reference() {
let nb = NegativeBinomial::new(1.0_f64, 0.5).expect("valid params");
let geo = Geometric::new(0.5_f64).expect("valid params");
for &k in &[0.0_f64, 1.0, 2.0, 3.0, 4.0] {
let nb_pmf = nb.pmf(k);
let geo_pmf = geo.pmf(k);
assert!(
(nb_pmf - geo_pmf).abs() < 1e-9,
"NegBinom(1,0.5) pmf({k})={nb_pmf} != Geometric(0.5) pmf({k})={geo_pmf}"
);
}
}
#[test]
fn test_hypergeometric_reference() {
let dist = Hypergeometric::new(20, 7, 12, 0.0_f64).expect("valid params");
for k in 0..=7_u32 {
let pmf = dist.pmf(k as f64);
assert!(
(0.0..=1.0).contains(&pmf),
"Hypergeometric pmf({k}) out of [0,1]: {pmf}"
);
}
let total: f64 = (0..=7).map(|k| dist.pmf(k as f64)).sum();
assert!(
(total - 1.0).abs() < 1e-9,
"Hypergeometric pmf sum ≈ 1: got {total}"
);
let mut prev_cdf = 0.0_f64;
for k in 0..=7_u32 {
let cdf_k = dist.cdf(k as f64);
assert!(
cdf_k >= prev_cdf - 1e-12,
"Hypergeometric CDF non-monotone at k={k}"
);
assert!(
(0.0..=(1.0 + 1e-12)).contains(&cdf_k),
"Hypergeometric CDF out of bounds at k={k}"
);
prev_cdf = cdf_k;
}
}
#[test]
fn test_hypergeometric_small_reference() {
let dist = Hypergeometric::new(10, 4, 5, 0.0_f64).expect("valid params");
let pmf2 = dist.pmf(2.0);
assert!(
check_pdf(pmf2, 0.47619047619047616, 1e-9, "Hypergeom(10,4,5)", 2.0),
"Hypergeom(10,4,5) pmf(2) = {pmf2}"
);
let mean = dist.mean();
assert!((mean - 2.0).abs() < 1e-9, "Hypergeom(10,4,5) mean = {mean}");
}