1use crate::percentiles::percentile;
5
6pub fn conditional_tail_expectation(samples: &[u64], q: f64) -> u64 {
14 if samples.is_empty() {
15 return 0;
16 }
17 let mut sorted = samples.to_vec();
18 sorted.sort_unstable();
19 let cutoff = percentile(&sorted, q);
20 let tail: Vec<u64> = sorted.into_iter().filter(|&v| v > cutoff).collect();
21 if tail.is_empty() {
22 return cutoff;
23 }
24 tail.iter().sum::<u64>() / tail.len() as u64
25}
26
27pub fn hill_tail_index(samples: &[u64], k: usize) -> Option<f64> {
35 if k < 2 || samples.len() <= k {
36 return None;
37 }
38 let mut sorted = samples.to_vec();
39 sorted.sort_unstable();
40 let top: Vec<u64> = sorted.iter().rev().take(k + 1).copied().collect();
41 let pivot = top[k];
42 if pivot == 0 {
43 return None;
44 }
45 let pivot_f = pivot as f64;
46 let mut sum = 0.0;
47 for v in top.iter().take(k) {
48 let ratio = (*v as f64) / pivot_f;
49 if ratio > 0.0 {
50 sum += ratio.ln();
51 }
52 }
53 Some(sum / k as f64)
54}
55
56pub fn tail_fatness_ratio(samples: &[u64]) -> f64 {
60 if samples.is_empty() {
61 return 0.0;
62 }
63 let mut sorted = samples.to_vec();
64 sorted.sort_unstable();
65 let p50 = percentile(&sorted, 0.50);
66 let p99 = percentile(&sorted, 0.99);
67 if p50 == 0 {
68 return 0.0;
69 }
70 p99 as f64 / p50 as f64
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn cte_exceeds_quantile() {
79 let v: Vec<u64> = (0..100).collect();
80 let cte99 = conditional_tail_expectation(&v, 0.95);
81 assert!(cte99 >= 95);
82 }
83
84 #[test]
85 fn cte_empty_is_zero() {
86 assert_eq!(conditional_tail_expectation(&[], 0.99), 0);
87 }
88
89 #[test]
90 fn hill_returns_none_for_tiny_input() {
91 assert!(hill_tail_index(&[1, 2, 3], 5).is_none());
92 }
93
94 #[test]
95 fn hill_powerlike_tail_returns_positive() {
96 let v: Vec<u64> = (1..1000).map(|i| (i as u64).pow(2)).collect();
97 let idx = hill_tail_index(&v, 50).unwrap();
98 assert!(
99 idx > 0.0,
100 "Hill estimator on power-law tail should be positive: {}",
101 idx
102 );
103 }
104
105 #[test]
106 fn fatness_ratio_uniform_close_to_one() {
107 let v = vec![100u64; 1000];
108 let r = tail_fatness_ratio(&v);
109 assert!((r - 1.0).abs() < 0.01);
110 }
111
112 #[test]
113 fn fatness_ratio_heavy_tail_exceeds_one() {
114 let mut v: Vec<u64> = vec![100; 990];
115 v.resize(v.len() + 10, 10_000);
116 let r = tail_fatness_ratio(&v);
117 assert!(r > 1.0);
118 }
119}