sim_lib_numbers_stats/
parametric_distribution.rs1use super::{StatsError, StatsResult};
4use sim_lib_numbers_special::{erfc, log_gamma, regularized_beta};
5
6fn valid_probability(metric: &'static str, value: f64) -> StatsResult<()> {
7 if value.is_finite() && (0.0..=1.0).contains(&value) {
8 Ok(())
9 } else {
10 Err(StatsError::ProbabilityOutOfRange {
11 metric,
12 index: None,
13 value,
14 })
15 }
16}
17fn quantile_root(mut lower: f64, mut upper: f64, p: f64, cdf: impl Fn(f64) -> f64) -> f64 {
18 for _ in 0..192 {
19 let middle = lower + (upper - lower) / 2.0;
20 if cdf(middle) < p {
21 lower = middle;
22 } else {
23 upper = middle;
24 }
25 if upper - lower <= 2.0 * f64::EPSILON * middle.abs().max(1.0) {
26 break;
27 }
28 }
29 lower + (upper - lower) / 2.0
30}
31
32pub fn normal_density(x: f64) -> f64 {
34 (-0.5 * x * x).exp() / (2.0 * std::f64::consts::PI).sqrt()
35}
36pub fn normal_cdf(x: f64) -> f64 {
38 0.5 * erfc(-x / std::f64::consts::SQRT_2).value
39}
40pub fn normal_survival(x: f64) -> f64 {
42 0.5 * erfc(x / std::f64::consts::SQRT_2).value
43}
44pub fn normal_quantile(p: f64) -> StatsResult<f64> {
46 valid_probability("normal_quantile", p)?;
47 if p == 0.0 {
48 return Ok(f64::NEG_INFINITY);
49 }
50 if p == 1.0 {
51 return Ok(f64::INFINITY);
52 }
53 Ok(quantile_root(-40.0, 40.0, p, normal_cdf))
54}
55
56pub fn student_t_density(x: f64, v: f64) -> StatsResult<f64> {
58 if !(v > 0.0 && v.is_finite()) {
59 return Err(StatsError::InvalidControl {
60 field: "degrees_of_freedom",
61 reason: "must be finite and positive",
62 });
63 }
64 let lg1 = log_gamma((v + 1.0) / 2.0)
65 .map_err(|_| StatsError::InvalidControl {
66 field: "degrees_of_freedom",
67 reason: "gamma evaluation failed",
68 })?
69 .value;
70 let lg0 = log_gamma(v / 2.0)
71 .map_err(|_| StatsError::InvalidControl {
72 field: "degrees_of_freedom",
73 reason: "gamma evaluation failed",
74 })?
75 .value;
76 Ok(
77 (lg1 - lg0 - 0.5 * (v * std::f64::consts::PI).ln() - 0.5 * (v + 1.0) * (x * x / v).ln_1p())
78 .exp(),
79 )
80}
81fn student_tail(x: f64, v: f64) -> StatsResult<f64> {
82 if !(v > 0.0 && v.is_finite()) {
83 return Err(StatsError::InvalidControl {
84 field: "degrees_of_freedom",
85 reason: "must be finite and positive",
86 });
87 }
88 regularized_beta(v / 2.0, 0.5, v / (v + x * x))
89 .map(|r| 0.5 * r.value)
90 .map_err(|_| StatsError::InvalidControl {
91 field: "degrees_of_freedom",
92 reason: "beta evaluation failed",
93 })
94}
95pub fn student_t_cdf(x: f64, v: f64) -> StatsResult<f64> {
97 let tail = student_tail(x, v)?;
98 Ok(if x >= 0.0 { 1.0 - tail } else { tail })
99}
100pub fn student_t_survival(x: f64, v: f64) -> StatsResult<f64> {
102 let tail = student_tail(x, v)?;
103 Ok(if x >= 0.0 { tail } else { 1.0 - tail })
104}
105pub fn student_t_quantile(p: f64, v: f64) -> StatsResult<f64> {
107 valid_probability("student_t_quantile", p)?;
108 if p == 0.0 {
109 return Ok(f64::NEG_INFINITY);
110 }
111 if p == 1.0 {
112 return Ok(f64::INFINITY);
113 }
114 student_t_density(0.0, v)?;
115 let mut bound = 1.0;
116 while (student_t_cdf(-bound, v)? > p || student_t_cdf(bound, v)? < p) && bound < 1e16 {
117 bound *= 2.0;
118 }
119 Ok(quantile_root(-bound, bound, p, |x| {
120 student_t_cdf(x, v).unwrap_or(f64::NAN)
121 }))
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 fn close(a: f64, b: f64, t: f64) {
128 assert!((a - b).abs() < t * b.abs().max(1.0), "{a} != {b}");
129 }
130 #[test]
131 fn normal_center_tails_and_inversion() {
132 close(normal_density(0.0), 0.3989422804014327, 1e-15);
133 close(normal_cdf(0.0), 0.5, 1e-15);
134 assert!(normal_survival(10.0) > 0.0);
135 for p in [1e-10, 0.01, 0.5, 0.99, 1.0 - 1e-10] {
136 close(normal_cdf(normal_quantile(p).unwrap()), p, 3e-8);
137 }
138 }
139 #[test]
140 fn student_symmetry_tails_and_inversion() {
141 for v in [1.0, 5.0, 30.0] {
142 close(student_t_cdf(0.0, v).unwrap(), 0.5, 1e-14);
143 assert!(student_t_survival(20.0, v).unwrap() > 0.0);
144 for p in [1e-5, 0.1, 0.9, 1.0 - 1e-5] {
145 let x = student_t_quantile(p, v).unwrap();
146 close(student_t_cdf(x, v).unwrap(), p, 2e-11);
147 }
148 }
149 }
150}