scirs2_stats/distributions/validation.rs
1//! Distribution reference validation framework
2//!
3//! This module provides a systematic harness for testing statistical distributions
4//! against known reference values derived from SciPy documentation.
5//!
6//! # Overview
7//!
8//! The validation framework consists of:
9//! - [`DistributionRefPoint`]: a single (x, pdf, cdf, ppf) reference triplet
10//! - [`DistributionValidation`]: a named collection of reference points with tolerances
11//! - Helper functions [`check_pdf`], [`check_cdf`], [`check_ppf`] that compare computed
12//! values against references and report failures with contextual messages
13//!
14//! # Usage
15//!
16//! ```rust
17//! use scirs2_stats::distributions::validation::{check_pdf, check_cdf};
18//! let ok = check_pdf(0.398942, 0.398942280401433, 1e-6, "Normal(0,1)", 0.0);
19//! assert!(ok);
20//! ```
21
22/// A single reference data point for a distribution.
23///
24/// Each field is `Option<f64>` so that only the values that are meaningful
25/// (or can be verified) need to be populated.
26#[derive(Debug, Clone)]
27pub struct DistributionRefPoint {
28 /// The x-value (or probability p for PPF)
29 pub x: f64,
30 /// Expected PDF value at x (continuous distributions only)
31 pub pdf: Option<f64>,
32 /// Expected CDF value at x
33 pub cdf: Option<f64>,
34 /// Expected PPF/quantile value when x is treated as probability p
35 pub ppf: Option<f64>,
36}
37
38/// A named collection of reference points and tolerances for one distribution configuration.
39#[derive(Debug, Clone)]
40pub struct DistributionValidation {
41 /// Human-readable name such as `"Normal(0,1)"` or `"Gamma(2,1)"`
42 pub name: &'static str,
43 /// Reference points to validate against
44 pub points: &'static [DistributionRefPoint],
45 /// Absolute tolerance for comparisons
46 pub abs_tol: f64,
47 /// Relative tolerance for comparisons (used when abs_tol check fails)
48 pub rel_tol: f64,
49}
50
51/// Check that an actual PDF value matches the expected reference within tolerance.
52///
53/// Returns `true` if the absolute error is within `abs_tol`. When it does not,
54/// the function also prints a diagnostic line (using `eprintln!`) so that test
55/// output contains useful context even when no panic occurs.
56///
57/// # Arguments
58///
59/// * `actual` - Value returned by the distribution's `pdf()` method
60/// * `expected` - SciPy-derived reference value
61/// * `abs_tol` - Maximum permitted absolute error
62/// * `dist_name` - Short description used in the diagnostic message
63/// * `x` - The x-argument at which the PDF was evaluated
64pub fn check_pdf(actual: f64, expected: f64, abs_tol: f64, dist_name: &str, x: f64) -> bool {
65 let err = (actual - expected).abs();
66 if err <= abs_tol {
67 return true;
68 }
69 eprintln!(
70 "[FAIL] {dist_name} pdf({x}): actual={actual:.15}, expected={expected:.15}, err={err:.3e}, tol={abs_tol:.3e}"
71 );
72 false
73}
74
75/// Check that an actual CDF value matches the expected reference within tolerance.
76///
77/// Returns `true` if the absolute error is within `abs_tol`.
78///
79/// # Arguments
80///
81/// * `actual` - Value returned by the distribution's `cdf()` method
82/// * `expected` - SciPy-derived reference value
83/// * `abs_tol` - Maximum permitted absolute error
84/// * `dist_name` - Short description used in the diagnostic message
85/// * `x` - The x-argument at which the CDF was evaluated
86pub fn check_cdf(actual: f64, expected: f64, abs_tol: f64, dist_name: &str, x: f64) -> bool {
87 let err = (actual - expected).abs();
88 if err <= abs_tol {
89 return true;
90 }
91 eprintln!(
92 "[FAIL] {dist_name} cdf({x}): actual={actual:.15}, expected={expected:.15}, err={err:.3e}, tol={abs_tol:.3e}"
93 );
94 false
95}
96
97/// Check that a PPF (quantile) value matches the expected reference within tolerance.
98///
99/// Returns `true` if the absolute error is within `abs_tol`.
100///
101/// # Arguments
102///
103/// * `actual` - Value returned by the distribution's `ppf()` method
104/// * `expected` - SciPy-derived reference value
105/// * `abs_tol` - Maximum permitted absolute error
106/// * `dist_name` - Short description used in the diagnostic message
107/// * `p` - The probability argument at which the PPF was evaluated
108pub fn check_ppf(actual: f64, expected: f64, abs_tol: f64, dist_name: &str, p: f64) -> bool {
109 let err = (actual - expected).abs();
110 if err <= abs_tol {
111 return true;
112 }
113 eprintln!(
114 "[FAIL] {dist_name} ppf({p}): actual={actual:.15}, expected={expected:.15}, err={err:.3e}, tol={abs_tol:.3e}"
115 );
116 false
117}