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)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct Tail {
21 #[cfg_attr(feature = "serde", serde(with = "crate::ser::nan_safe_f64"))]
23 gamma: f64,
24 #[cfg_attr(feature = "serde", serde(with = "crate::ser::nan_safe_f64"))]
26 sigma: f64,
27 peaks: Peaks,
29}
30
31impl Tail {
32 pub fn new(size: usize) -> SpotResult<Self> {
34 Ok(Self {
35 gamma: f64::NAN,
36 sigma: f64::NAN,
37 peaks: Peaks::new(size)?,
38 })
39 }
40
41 pub fn push(&mut self, x: f64) {
43 self.peaks.push(x);
44 }
45
46 pub fn fit(&mut self) -> f64 {
49 if self.peaks.size() == 0 {
50 return f64::NAN;
51 }
52
53 let mut max_llhood = f64::NAN;
55 let mut tmp_gamma;
56 let mut tmp_sigma;
57
58 let llhood = {
60 let (gamma, sigma, llhood) = mom_estimator(&self.peaks);
61 tmp_gamma = gamma;
62 tmp_sigma = sigma;
63 llhood
64 };
65
66 if max_llhood.is_nan() || llhood > max_llhood {
67 max_llhood = llhood;
68 self.gamma = tmp_gamma;
69 self.sigma = tmp_sigma;
70 }
71
72 let llhood = {
74 let (gamma, sigma, llhood) = grimshaw_estimator(&self.peaks);
75 tmp_gamma = gamma;
76 tmp_sigma = sigma;
77 llhood
78 };
79
80 if max_llhood.is_nan() || llhood > max_llhood {
81 max_llhood = llhood;
83 self.gamma = tmp_gamma;
84 self.sigma = tmp_sigma;
85 }
86
87 max_llhood
88 }
89
90 pub fn probability(&self, s: f64, d: f64) -> f64 {
92 if self.gamma.is_nan() || self.sigma.is_nan() || self.sigma <= 0.0 {
93 return f64::NAN;
94 }
95
96 if self.gamma == 0.0 {
98 s * xexp(-d / self.sigma)
99 } else {
100 let r = d * (self.gamma / self.sigma);
101 s * xpow(1.0 + r, -1.0 / self.gamma)
102 }
103 }
104
105 pub fn quantile(&self, s: f64, q: f64) -> f64 {
109 if self.gamma.is_nan() || self.sigma.is_nan() || self.sigma <= 0.0 {
110 return f64::NAN;
111 }
112
113 let r = q / s;
114 if self.gamma == 0.0 {
116 -self.sigma * xlog(r)
117 } else {
118 (self.sigma / self.gamma) * (xpow(r, -self.gamma) - 1.0)
119 }
120 }
121
122 pub fn gamma(&self) -> f64 {
124 self.gamma
125 }
126
127 pub fn sigma(&self) -> f64 {
129 self.sigma
130 }
131
132 pub fn size(&self) -> usize {
134 self.peaks.size()
135 }
136
137 pub fn peaks(&self) -> &Peaks {
139 &self.peaks
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use crate::error::SpotError;
147
148 #[test]
149 fn test_tail_creation() {
150 let tail = Tail::new(10).unwrap();
151 assert_eq!(tail.size(), 0);
152 assert!(tail.gamma().is_nan());
153 assert!(tail.sigma().is_nan());
154 }
155
156 #[test]
157 fn test_tail_zero_size() {
158 let result = Tail::new(0);
159 assert!(result.is_err());
160 assert_eq!(result.unwrap_err(), SpotError::MemoryAllocationFailed);
161 }
162
163 #[test]
164 fn test_tail_push() {
165 let mut tail = Tail::new(5).unwrap();
166
167 tail.push(1.0);
168 assert_eq!(tail.size(), 1);
169
170 tail.push(2.0);
171 tail.push(3.0);
172 assert_eq!(tail.size(), 3);
173 }
174
175 #[test]
176 fn test_tail_fit_empty() {
177 let mut tail = Tail::new(5).unwrap();
178 let llhood = tail.fit();
179 assert!(llhood.is_nan());
180 assert!(tail.gamma().is_nan());
181 assert!(tail.sigma().is_nan());
182 }
183
184 #[test]
185 fn test_tail_fit_with_data() {
186 let mut tail = Tail::new(10).unwrap();
187
188 for value in [1.0, 1.5, 2.0, 2.5, 3.0, 1.2, 1.8, 2.2] {
190 tail.push(value);
191 }
192
193 let llhood = tail.fit();
194 assert!(!llhood.is_nan());
195 assert!(llhood.is_finite());
196
197 assert!(!tail.gamma().is_nan());
199 assert!(!tail.sigma().is_nan());
200 assert!(tail.sigma() > 0.0); }
202
203 #[test]
204 fn test_tail_quantile_gamma_zero() {
205 let mut tail = Tail::new(10).unwrap();
206
207 tail.gamma = 0.0;
209 tail.sigma = 1.0;
210
211 let q = tail.quantile(0.1, 0.01);
212 assert!(!q.is_nan());
213 assert!(q > 0.0); }
215
216 #[test]
217 fn test_tail_quantile_gamma_nonzero() {
218 let mut tail = Tail::new(10).unwrap();
219
220 tail.gamma = 0.1;
222 tail.sigma = 1.0;
223
224 let q = tail.quantile(0.1, 0.01);
225 assert!(!q.is_nan());
226 assert!(q.is_finite());
227 }
228
229 #[test]
230 fn test_tail_probability_gamma_zero() {
231 let mut tail = Tail::new(10).unwrap();
232
233 tail.gamma = 0.0;
235 tail.sigma = 1.0;
236
237 let p = tail.probability(0.1, 2.0);
238 assert!(!p.is_nan());
239 assert!((0.0..=0.1).contains(&p));
240 }
241
242 #[test]
243 fn test_tail_probability_gamma_nonzero() {
244 let mut tail = Tail::new(10).unwrap();
245
246 tail.gamma = 0.1;
248 tail.sigma = 1.0;
249
250 let p = tail.probability(0.1, 2.0);
251 assert!(!p.is_nan());
252 assert!(p >= 0.0);
253 }
254
255 #[test]
256 fn test_tail_invalid_parameters() {
257 let mut tail = Tail::new(10).unwrap();
258
259 tail.gamma = 0.1;
261 tail.sigma = 0.0;
262
263 let q = tail.quantile(0.1, 0.01);
264 assert!(q.is_nan());
265
266 let p = tail.probability(0.1, 2.0);
267 assert!(p.is_nan());
268 }
269
270 #[test]
271 fn test_tail_consistency() {
272 let mut tail = Tail::new(10).unwrap();
273
274 for value in [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0] {
276 tail.push(value);
277 }
278
279 let _llhood = tail.fit();
280
281 let s = 0.1;
283 let q = 0.01;
284 let quantile_val = tail.quantile(s, q);
285
286 if !quantile_val.is_nan() && quantile_val.is_finite() {
287 let prob_val = tail.probability(s, quantile_val);
288 if !prob_val.is_nan() && prob_val.is_finite() {
289 assert!((prob_val - q).abs() < q * 0.1 || prob_val < q * 2.0);
292 }
293 }
294 }
295}