1use crate::error::SpotResult;
7
8use crate::estimator::{grimshaw_estimator, mom_estimator};
9use crate::math::{xexp, xlog, xpow};
10use crate::peaks::Peaks;
11
12#[derive(Debug, Clone)]
14pub struct Tail {
15 gamma: f64,
17 sigma: f64,
19 peaks: Peaks,
21}
22
23impl Tail {
24 pub fn new(size: usize) -> SpotResult<Self> {
26 Ok(Self {
27 gamma: f64::NAN,
28 sigma: f64::NAN,
29 peaks: Peaks::new(size)?,
30 })
31 }
32
33 pub fn push(&mut self, x: f64) {
35 self.peaks.push(x);
36 }
37
38 pub fn fit(&mut self) -> f64 {
41 if self.peaks.size() == 0 {
42 return f64::NAN;
43 }
44
45 let mut max_llhood = f64::NAN;
47 let mut tmp_gamma;
48 let mut tmp_sigma;
49
50 let llhood = {
52 let (gamma, sigma, llhood) = mom_estimator(&self.peaks);
53 tmp_gamma = gamma;
54 tmp_sigma = sigma;
55 llhood
56 };
57
58 if max_llhood.is_nan() || llhood > max_llhood {
59 max_llhood = llhood;
60 self.gamma = tmp_gamma;
61 self.sigma = tmp_sigma;
62 }
63
64 let llhood = {
66 let (gamma, sigma, llhood) = grimshaw_estimator(&self.peaks);
67 tmp_gamma = gamma;
68 tmp_sigma = sigma;
69 llhood
70 };
71
72 if max_llhood.is_nan() || llhood > max_llhood {
73 max_llhood = llhood;
75 self.gamma = tmp_gamma;
76 self.sigma = tmp_sigma;
77 }
78
79 max_llhood
80 }
81
82 pub fn probability(&self, s: f64, d: f64) -> f64 {
84 if self.gamma.is_nan() || self.sigma.is_nan() || self.sigma <= 0.0 {
85 return f64::NAN;
86 }
87
88 if self.gamma == 0.0 {
90 s * xexp(-d / self.sigma)
91 } else {
92 let r = d * (self.gamma / self.sigma);
93 s * xpow(1.0 + r, -1.0 / self.gamma)
94 }
95 }
96
97 pub fn quantile(&self, s: f64, q: f64) -> f64 {
101 if self.gamma.is_nan() || self.sigma.is_nan() || self.sigma <= 0.0 {
102 return f64::NAN;
103 }
104
105 let r = q / s;
106 if self.gamma == 0.0 {
108 -self.sigma * xlog(r)
109 } else {
110 (self.sigma / self.gamma) * (xpow(r, -self.gamma) - 1.0)
111 }
112 }
113
114 pub fn gamma(&self) -> f64 {
116 self.gamma
117 }
118
119 pub fn sigma(&self) -> f64 {
121 self.sigma
122 }
123
124 pub fn size(&self) -> usize {
126 self.peaks.size()
127 }
128
129 pub fn peaks(&self) -> &Peaks {
131 &self.peaks
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::error::SpotError;
139
140 #[test]
141 fn test_tail_creation() {
142 let tail = Tail::new(10).unwrap();
143 assert_eq!(tail.size(), 0);
144 assert!(tail.gamma().is_nan());
145 assert!(tail.sigma().is_nan());
146 }
147
148 #[test]
149 fn test_tail_zero_size() {
150 let result = Tail::new(0);
151 assert!(result.is_err());
152 assert_eq!(result.unwrap_err(), SpotError::MemoryAllocationFailed);
153 }
154
155 #[test]
156 fn test_tail_push() {
157 let mut tail = Tail::new(5).unwrap();
158
159 tail.push(1.0);
160 assert_eq!(tail.size(), 1);
161
162 tail.push(2.0);
163 tail.push(3.0);
164 assert_eq!(tail.size(), 3);
165 }
166
167 #[test]
168 fn test_tail_fit_empty() {
169 let mut tail = Tail::new(5).unwrap();
170 let llhood = tail.fit();
171 assert!(llhood.is_nan());
172 assert!(tail.gamma().is_nan());
173 assert!(tail.sigma().is_nan());
174 }
175
176 #[test]
177 fn test_tail_fit_with_data() {
178 let mut tail = Tail::new(10).unwrap();
179
180 for value in [1.0, 1.5, 2.0, 2.5, 3.0, 1.2, 1.8, 2.2] {
182 tail.push(value);
183 }
184
185 let llhood = tail.fit();
186 assert!(!llhood.is_nan());
187 assert!(llhood.is_finite());
188
189 assert!(!tail.gamma().is_nan());
191 assert!(!tail.sigma().is_nan());
192 assert!(tail.sigma() > 0.0); }
194
195 #[test]
196 fn test_tail_quantile_gamma_zero() {
197 let mut tail = Tail::new(10).unwrap();
198
199 tail.gamma = 0.0;
201 tail.sigma = 1.0;
202
203 let q = tail.quantile(0.1, 0.01);
204 assert!(!q.is_nan());
205 assert!(q > 0.0); }
207
208 #[test]
209 fn test_tail_quantile_gamma_nonzero() {
210 let mut tail = Tail::new(10).unwrap();
211
212 tail.gamma = 0.1;
214 tail.sigma = 1.0;
215
216 let q = tail.quantile(0.1, 0.01);
217 assert!(!q.is_nan());
218 assert!(q.is_finite());
219 }
220
221 #[test]
222 fn test_tail_probability_gamma_zero() {
223 let mut tail = Tail::new(10).unwrap();
224
225 tail.gamma = 0.0;
227 tail.sigma = 1.0;
228
229 let p = tail.probability(0.1, 2.0);
230 assert!(!p.is_nan());
231 assert!((0.0..=0.1).contains(&p));
232 }
233
234 #[test]
235 fn test_tail_probability_gamma_nonzero() {
236 let mut tail = Tail::new(10).unwrap();
237
238 tail.gamma = 0.1;
240 tail.sigma = 1.0;
241
242 let p = tail.probability(0.1, 2.0);
243 assert!(!p.is_nan());
244 assert!(p >= 0.0);
245 }
246
247 #[test]
248 fn test_tail_invalid_parameters() {
249 let mut tail = Tail::new(10).unwrap();
250
251 tail.gamma = 0.1;
253 tail.sigma = 0.0;
254
255 let q = tail.quantile(0.1, 0.01);
256 assert!(q.is_nan());
257
258 let p = tail.probability(0.1, 2.0);
259 assert!(p.is_nan());
260 }
261
262 #[test]
263 fn test_tail_consistency() {
264 let mut tail = Tail::new(10).unwrap();
265
266 for value in [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0] {
268 tail.push(value);
269 }
270
271 let _llhood = tail.fit();
272
273 let s = 0.1;
275 let q = 0.01;
276 let quantile_val = tail.quantile(s, q);
277
278 if !quantile_val.is_nan() && quantile_val.is_finite() {
279 let prob_val = tail.probability(s, quantile_val);
280 if !prob_val.is_nan() && prob_val.is_finite() {
281 assert!((prob_val - q).abs() < q * 0.1 || prob_val < q * 2.0);
284 }
285 }
286 }
287}