Skip to main content

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}