1use crate::dist::{Distribution, DistributionMethods, RegressionDistn};
2use crate::scores::{CRPScore, LogScore, Scorable};
3use ndarray::{array, Array1, Array2, Array3};
4use rand::prelude::*;
5
6#[derive(Debug, Clone)]
8pub struct Laplace {
9 pub loc: Array1<f64>,
11 pub scale: Array1<f64>,
13 _params: Array2<f64>,
15}
16
17impl Distribution for Laplace {
18 fn from_params(params: &Array2<f64>) -> Self {
19 let loc = params.column(0).to_owned();
20 let scale = params.column(1).mapv(f64::exp);
21 Laplace {
22 loc,
23 scale,
24 _params: params.clone(),
25 }
26 }
27
28 fn fit(y: &Array1<f64>) -> Array1<f64> {
29 let n = y.len();
31 if n == 0 {
32 return array![0.0, 0.0];
33 }
34
35 let mut sorted: Vec<f64> = y.to_vec();
37 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
38 let median = if n % 2 == 0 {
39 (sorted[n / 2 - 1] + sorted[n / 2]) / 2.0
40 } else {
41 sorted[n / 2]
42 };
43
44 let mad: f64 = y.iter().map(|&x| (x - median).abs()).sum::<f64>() / n as f64;
46
47 array![median, mad.max(1e-6).ln()]
48 }
49
50 fn n_params(&self) -> usize {
51 2
52 }
53
54 fn predict(&self) -> Array1<f64> {
55 self.loc.clone()
57 }
58
59 fn params(&self) -> &Array2<f64> {
60 &self._params
61 }
62}
63
64impl RegressionDistn for Laplace {}
65
66impl DistributionMethods for Laplace {
67 fn mean(&self) -> Array1<f64> {
68 self.loc.clone()
69 }
70
71 fn variance(&self) -> Array1<f64> {
72 2.0 * &self.scale * &self.scale
74 }
75
76 fn std(&self) -> Array1<f64> {
77 self.variance().mapv(f64::sqrt)
78 }
79
80 fn pdf(&self, y: &Array1<f64>) -> Array1<f64> {
81 let mut result = Array1::zeros(y.len());
83 for i in 0..y.len() {
84 let abs_diff = (y[i] - self.loc[i]).abs();
85 result[i] = (-abs_diff / self.scale[i]).exp() / (2.0 * self.scale[i]);
86 }
87 result
88 }
89
90 fn logpdf(&self, y: &Array1<f64>) -> Array1<f64> {
91 let mut result = Array1::zeros(y.len());
93 for i in 0..y.len() {
94 let abs_diff = (y[i] - self.loc[i]).abs();
95 result[i] = -abs_diff / self.scale[i] - (2.0 * self.scale[i]).ln();
96 }
97 result
98 }
99
100 fn cdf(&self, y: &Array1<f64>) -> Array1<f64> {
101 let mut result = Array1::zeros(y.len());
105 for i in 0..y.len() {
106 let diff = y[i] - self.loc[i];
107 if diff < 0.0 {
108 result[i] = 0.5 * (diff / self.scale[i]).exp();
109 } else {
110 result[i] = 1.0 - 0.5 * (-diff / self.scale[i]).exp();
111 }
112 }
113 result
114 }
115
116 fn ppf(&self, q: &Array1<f64>) -> Array1<f64> {
117 let mut result = Array1::zeros(q.len());
121 for i in 0..q.len() {
122 let q_clamped = q[i].clamp(1e-15, 1.0 - 1e-15);
123 if q_clamped < 0.5 {
124 result[i] = self.loc[i] + self.scale[i] * (2.0 * q_clamped).ln();
125 } else {
126 result[i] = self.loc[i] - self.scale[i] * (2.0 * (1.0 - q_clamped)).ln();
127 }
128 }
129 result
130 }
131
132 fn sample(&self, n_samples: usize) -> Array2<f64> {
133 let n_obs = self.loc.len();
134 let mut samples = Array2::zeros((n_samples, n_obs));
135 let mut rng = rand::rng();
136
137 for i in 0..n_obs {
138 for s in 0..n_samples {
139 let u: f64 = rng.random();
141 if u < 0.5 {
142 samples[[s, i]] = self.loc[i] + self.scale[i] * (2.0 * u).ln();
143 } else {
144 samples[[s, i]] = self.loc[i] - self.scale[i] * (2.0 * (1.0 - u)).ln();
145 }
146 }
147 }
148 samples
149 }
150
151 fn median(&self) -> Array1<f64> {
152 self.loc.clone()
154 }
155
156 fn mode(&self) -> Array1<f64> {
157 self.loc.clone()
159 }
160}
161
162impl Scorable<LogScore> for Laplace {
163 fn score(&self, y: &Array1<f64>) -> Array1<f64> {
164 let mut scores = Array1::zeros(y.len());
166 for i in 0..y.len() {
167 let abs_diff = (y[i] - self.loc[i]).abs();
168 scores[i] = abs_diff / self.scale[i] + (2.0 * self.scale[i]).ln();
169 }
170 scores
171 }
172
173 fn d_score(&self, y: &Array1<f64>) -> Array2<f64> {
174 let n_obs = y.len();
175 let mut d_params = Array2::zeros((n_obs, 2));
176
177 for i in 0..n_obs {
178 let diff = self.loc[i] - y[i];
179 d_params[[i, 0]] = diff.signum() / self.scale[i];
181 d_params[[i, 1]] = 1.0 - diff.abs() / self.scale[i];
183 }
184
185 d_params
186 }
187
188 fn metric(&self) -> Array3<f64> {
189 let n_obs = self.loc.len();
191 let mut fi = Array3::zeros((n_obs, 2, 2));
192
193 for i in 0..n_obs {
194 let scale_sq = self.scale[i] * self.scale[i];
195 fi[[i, 0, 0]] = 1.0 / scale_sq;
196 fi[[i, 1, 1]] = 1.0;
197 }
198
199 fi
200 }
201}
202
203impl Scorable<CRPScore> for Laplace {
204 fn score(&self, y: &Array1<f64>) -> Array1<f64> {
205 let mut scores = Array1::zeros(y.len());
208 for i in 0..y.len() {
209 let abs_diff = (y[i] - self.loc[i]).abs();
210 let exp_term = (-abs_diff / self.scale[i]).exp();
211 scores[i] = abs_diff + self.scale[i] * exp_term - 0.75 * self.scale[i];
212 }
213 scores
214 }
215
216 fn d_score(&self, y: &Array1<f64>) -> Array2<f64> {
217 let n_obs = y.len();
218 let mut d_params = Array2::zeros((n_obs, 2));
219
220 for i in 0..n_obs {
221 let diff = self.loc[i] - y[i];
222 let abs_diff = diff.abs();
223 let exp_term = (-abs_diff / self.scale[i]).exp();
224
225 d_params[[i, 0]] = diff.signum() * (1.0 - exp_term);
227
228 d_params[[i, 1]] = exp_term * (self.scale[i] + abs_diff) - 0.75 * self.scale[i];
230 }
231
232 d_params
233 }
234
235 fn metric(&self) -> Array3<f64> {
236 let n_obs = self.loc.len();
238 let mut fi = Array3::zeros((n_obs, 2, 2));
239
240 for i in 0..n_obs {
241 fi[[i, 0, 0]] = 0.5 / self.scale[i];
242 fi[[i, 1, 1]] = 0.25 * self.scale[i];
243 }
244
245 fi
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use approx::assert_relative_eq;
253
254 #[test]
255 fn test_laplace_distribution_methods() {
256 let params = Array2::from_shape_vec((2, 2), vec![0.0, 0.0, 5.0, 2.0_f64.ln()]).unwrap();
260 let dist = Laplace::from_params(¶ms);
261
262 let mean = dist.mean();
264 assert_relative_eq!(mean[0], 0.0, epsilon = 1e-10);
265 assert_relative_eq!(mean[1], 5.0, epsilon = 1e-10);
266
267 let var = dist.variance();
269 assert_relative_eq!(var[0], 2.0, epsilon = 1e-10); assert_relative_eq!(var[1], 8.0, epsilon = 1e-10); let median = dist.median();
274 assert_relative_eq!(median[0], 0.0, epsilon = 1e-10);
275 assert_relative_eq!(median[1], 5.0, epsilon = 1e-10);
276
277 let mode = dist.mode();
279 assert_relative_eq!(mode[0], 0.0, epsilon = 1e-10);
280 assert_relative_eq!(mode[1], 5.0, epsilon = 1e-10);
281 }
282
283 #[test]
284 fn test_laplace_cdf_ppf() {
285 let params = Array2::from_shape_vec((3, 2), vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]).unwrap();
287 let dist = Laplace::from_params(¶ms);
288
289 let y = Array1::from_vec(vec![0.0, 0.0, 0.0]);
291 let cdf = dist.cdf(&y);
292 assert_relative_eq!(cdf[0], 0.5, epsilon = 1e-10);
293
294 let q = Array1::from_vec(vec![0.5, 0.5, 0.5]);
296 let ppf = dist.ppf(&q);
297 assert_relative_eq!(ppf[0], 0.0, epsilon = 1e-10);
298
299 let q = Array1::from_vec(vec![0.25, 0.5, 0.75]);
301 let ppf = dist.ppf(&q);
302 let cdf_of_ppf = dist.cdf(&ppf);
303 assert_relative_eq!(cdf_of_ppf[0], 0.25, epsilon = 1e-10);
304 assert_relative_eq!(cdf_of_ppf[1], 0.5, epsilon = 1e-10);
305 assert_relative_eq!(cdf_of_ppf[2], 0.75, epsilon = 1e-10);
306 }
307
308 #[test]
309 fn test_laplace_pdf() {
310 let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
311 let dist = Laplace::from_params(¶ms);
312
313 let y = Array1::from_vec(vec![0.0]);
315 let pdf = dist.pdf(&y);
316 assert_relative_eq!(pdf[0], 0.5, epsilon = 1e-10);
317
318 let logpdf = dist.logpdf(&y);
320 assert_relative_eq!(logpdf[0], 0.5_f64.ln(), epsilon = 1e-10);
321 }
322
323 #[test]
324 fn test_laplace_sample() {
325 let params = Array2::from_shape_vec((1, 2), vec![5.0, 1.0_f64.ln()]).unwrap();
326 let dist = Laplace::from_params(¶ms);
327
328 let samples = dist.sample(1000);
329 assert_eq!(samples.shape(), &[1000, 1]);
330
331 let sample_mean: f64 = samples.column(0).mean().unwrap();
333 assert!((sample_mean - 5.0).abs() < 0.3);
334
335 let mut sample_vec: Vec<f64> = samples.column(0).to_vec();
337 sample_vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
338 let sample_median = sample_vec[500];
339 assert!((sample_median - 5.0).abs() < 0.3);
340 }
341
342 #[test]
343 fn test_laplace_fit() {
344 let y = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
345 let params = Laplace::fit(&y);
346 assert_eq!(params.len(), 2);
347 assert_relative_eq!(params[0], 3.0, epsilon = 1e-10);
349 }
350
351 #[test]
352 fn test_laplace_logscore() {
353 let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
354 let dist = Laplace::from_params(¶ms);
355
356 let y = Array1::from_vec(vec![0.0]);
357 let score = Scorable::<LogScore>::score(&dist, &y);
358
359 assert_relative_eq!(score[0], 2.0_f64.ln(), epsilon = 1e-10);
361 }
362
363 #[test]
364 fn test_laplace_crps() {
365 let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
366 let dist = Laplace::from_params(¶ms);
367
368 let y = Array1::from_vec(vec![0.0]);
369 let score = Scorable::<CRPScore>::score(&dist, &y);
370
371 assert_relative_eq!(score[0], 0.25, epsilon = 1e-10);
373 }
374
375 #[test]
376 fn test_laplace_crps_d_score() {
377 let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
378 let dist = Laplace::from_params(¶ms);
379
380 let y = Array1::from_vec(vec![1.0]);
381 let d_score = Scorable::<CRPScore>::d_score(&dist, &y);
382
383 assert!(d_score[[0, 0]].is_finite());
385 assert!(d_score[[0, 1]].is_finite());
386 }
387
388 #[test]
389 fn test_laplace_interval() {
390 let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
391 let dist = Laplace::from_params(¶ms);
392
393 let (lower, upper) = dist.interval(0.1);
394 assert!(lower[0] < 0.0);
395 assert!(upper[0] > 0.0);
396 assert_relative_eq!(lower[0], -upper[0], epsilon = 1e-10);
398 }
399
400 #[test]
401 fn test_laplace_survival_function() {
402 let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
403 let dist = Laplace::from_params(¶ms);
404
405 let y = Array1::from_vec(vec![0.0]);
407 let sf = dist.sf(&y);
408 assert_relative_eq!(sf[0], 0.5, epsilon = 1e-10);
409
410 let cdf = dist.cdf(&y);
412 assert_relative_eq!(sf[0] + cdf[0], 1.0, epsilon = 1e-10);
413 }
414}