1use crate::dist::{Distribution, DistributionMethods, RegressionDistn};
2use crate::scores::{CRPScore, LogScore, Scorable};
3use ndarray::{array, Array1, Array2, Array3};
4use rand::prelude::*;
5use statrs::distribution::{Continuous, ContinuousCDF, StudentsT as StudentsTDist};
6use statrs::function::beta::ln_beta;
7use statrs::function::gamma::digamma;
8
9#[derive(Debug, Clone)]
13pub struct StudentT {
14 pub loc: Array1<f64>,
16 pub scale: Array1<f64>,
18 pub var: Array1<f64>,
20 pub df: Array1<f64>,
22 _params: Array2<f64>,
24}
25
26impl Distribution for StudentT {
27 fn from_params(params: &Array2<f64>) -> Self {
28 let loc = params.column(0).to_owned();
29 let scale = params.column(1).mapv(f64::exp);
30 let var = &scale * &scale;
31 let df = params.column(2).mapv(f64::exp);
32 StudentT {
33 loc,
34 scale,
35 var,
36 df,
37 _params: params.clone(),
38 }
39 }
40
41 fn fit(y: &Array1<f64>) -> Array1<f64> {
42 let mean = y.mean().unwrap_or(0.0);
44 let std_dev = y.std(0.0).max(1e-6);
45 let df = 3.0_f64;
47 array![mean, std_dev.ln(), df.ln()]
48 }
49
50 fn n_params(&self) -> usize {
51 3
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 StudentT {}
65
66impl DistributionMethods for StudentT {
67 fn mean(&self) -> Array1<f64> {
68 let mut result = Array1::zeros(self.loc.len());
70 for i in 0..self.loc.len() {
71 if self.df[i] > 1.0 {
72 result[i] = self.loc[i];
73 } else {
74 result[i] = f64::NAN;
75 }
76 }
77 result
78 }
79
80 fn variance(&self) -> Array1<f64> {
81 let mut result = Array1::zeros(self.loc.len());
83 for i in 0..self.loc.len() {
84 if self.df[i] > 2.0 {
85 result[i] = self.var[i] * self.df[i] / (self.df[i] - 2.0);
86 } else if self.df[i] > 1.0 {
87 result[i] = f64::INFINITY;
88 } else {
89 result[i] = f64::NAN;
90 }
91 }
92 result
93 }
94
95 fn pdf(&self, y: &Array1<f64>) -> Array1<f64> {
96 let mut result = Array1::zeros(y.len());
97 for i in 0..y.len() {
98 if let Ok(d) = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]) {
99 result[i] = d.pdf(y[i]);
100 }
101 }
102 result
103 }
104
105 fn logpdf(&self, y: &Array1<f64>) -> Array1<f64> {
106 let mut result = Array1::zeros(y.len());
107 for i in 0..y.len() {
108 if let Ok(d) = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]) {
109 result[i] = d.ln_pdf(y[i]);
110 }
111 }
112 result
113 }
114
115 fn cdf(&self, y: &Array1<f64>) -> Array1<f64> {
116 let mut result = Array1::zeros(y.len());
117 for i in 0..y.len() {
118 if let Ok(d) = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]) {
119 result[i] = d.cdf(y[i]);
120 }
121 }
122 result
123 }
124
125 fn ppf(&self, q: &Array1<f64>) -> Array1<f64> {
126 let mut result = Array1::zeros(q.len());
127 for i in 0..q.len() {
128 if let Ok(d) = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]) {
129 let q_clamped = q[i].clamp(1e-15, 1.0 - 1e-15);
130 result[i] = d.inverse_cdf(q_clamped);
131 }
132 }
133 result
134 }
135
136 fn sample(&self, n_samples: usize) -> Array2<f64> {
137 let n_obs = self.loc.len();
138 let mut samples = Array2::zeros((n_samples, n_obs));
139 let mut rng = rand::rng();
140
141 for i in 0..n_obs {
142 if let Ok(d) = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]) {
143 for s in 0..n_samples {
144 let u: f64 = rng.random();
145 samples[[s, i]] = d.inverse_cdf(u);
146 }
147 }
148 }
149 samples
150 }
151
152 fn median(&self) -> Array1<f64> {
153 self.loc.clone()
155 }
156
157 fn mode(&self) -> Array1<f64> {
158 self.loc.clone()
160 }
161}
162
163impl StudentT {
164 pub fn sample(&self, n_samples: usize) -> Array2<f64> {
167 let n_obs = self.loc.len();
168 let mut samples = Array2::zeros((n_samples, n_obs));
169 let mut rng = rand::rng();
170
171 for i in 0..n_obs {
172 let d = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]).unwrap();
173 for s in 0..n_samples {
174 let u: f64 = rng.random();
176 samples[[s, i]] = d.inverse_cdf(u);
177 }
178 }
179 samples
180 }
181}
182
183impl Scorable<LogScore> for StudentT {
184 fn score(&self, y: &Array1<f64>) -> Array1<f64> {
185 let mut scores = Array1::zeros(y.len());
186 for (i, &y_i) in y.iter().enumerate() {
187 let d = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]).unwrap();
188 scores[i] = -d.ln_pdf(y_i);
189 }
190 scores
191 }
192
193 fn d_score(&self, y: &Array1<f64>) -> Array2<f64> {
194 let n_obs = y.len();
195 let mut d_params = Array2::zeros((n_obs, 3));
196
197 for i in 0..n_obs {
198 let loc_i = self.loc[i];
199 let var_i = self.var[i];
200 let df_i = self.df[i];
201 let y_i = y[i];
202
203 let diff = y_i - loc_i;
204 let diff_sq = diff * diff;
205
206 let denom = df_i * var_i + diff_sq;
208
209 d_params[[i, 0]] = -(df_i + 1.0) * diff / denom;
211
212 d_params[[i, 1]] = 1.0 - (df_i + 1.0) * diff_sq / denom;
214
215 let term_1 = (df_i / 2.0) * digamma((df_i + 1.0) / 2.0);
217 let term_2 = (-df_i / 2.0) * digamma(df_i / 2.0);
218 let term_3 = -0.5;
219 let term_4_1 = (-df_i / 2.0) * (1.0 + diff_sq / (df_i * var_i)).ln();
220 let term_4_2_num = (df_i + 1.0) * diff_sq;
221 let term_4_2_den = 2.0 * (df_i * var_i) * (1.0 + diff_sq / (df_i * var_i));
222
223 d_params[[i, 2]] = -(term_1 + term_2 + term_3 + term_4_1 + term_4_2_num / term_4_2_den);
224 }
225
226 d_params
227 }
228
229 fn metric(&self) -> Array3<f64> {
230 let n_obs = self.loc.len();
234 let n_params = 3;
235
236 let mut fi = Array3::zeros((n_obs, n_params, n_params));
237 for i in 0..n_obs {
238 for j in 0..n_params {
239 fi[[i, j, j]] = 1.0;
240 }
241 }
242 fi
243 }
244}
245
246impl Scorable<CRPScore> for StudentT {
247 fn score(&self, y: &Array1<f64>) -> Array1<f64> {
248 let mut scores = Array1::zeros(y.len());
258
259 for i in 0..y.len() {
260 let mu = self.loc[i];
261 let sigma = self.scale[i];
262 let nu = self.df[i];
263 let y_i = y[i];
264
265 if nu <= 1.0 {
267 scores[i] = 1e10; continue;
269 }
270
271 let z = (y_i - mu) / sigma;
272
273 if let Ok(std_t) = StudentsTDist::new(0.0, 1.0, nu) {
275 let f_z = std_t.pdf(z);
276 let big_f_z = std_t.cdf(z);
277
278 let term1 = z * (2.0 * big_f_z - 1.0);
280
281 let term2 = 2.0 * f_z * (nu + z * z) / (nu - 1.0);
283
284 let ln_b1 = ln_beta(0.5, nu - 0.5);
287 let ln_b2 = ln_beta(0.5, nu / 2.0);
288 let term3 = 2.0 * nu.sqrt() * (ln_b1 - 2.0 * ln_b2).exp() / (nu - 1.0);
289
290 scores[i] = sigma * (term1 + term2 - term3);
291 } else {
292 scores[i] = 1e10;
293 }
294 }
295 scores
296 }
297
298 fn d_score(&self, y: &Array1<f64>) -> Array2<f64> {
299 let n_obs = y.len();
302 let mut d_params = Array2::zeros((n_obs, 3));
303 let eps = 1e-6;
304
305 for i in 0..n_obs {
306 let mu = self.loc[i];
307 let sigma = self.scale[i];
308 let nu = self.df[i];
309 let y_i = y[i];
310
311 if nu <= 1.0 {
312 continue; }
314
315 let z = (y_i - mu) / sigma;
316
317 if let Ok(std_t) = StudentsTDist::new(0.0, 1.0, nu) {
318 let big_f_z = std_t.cdf(z);
319
320 d_params[[i, 0]] = -(2.0 * big_f_z - 1.0);
322
323 let f_z = std_t.pdf(z);
326 let term1 = z * (2.0 * big_f_z - 1.0);
327 let term2 = 2.0 * f_z * (nu + z * z) / (nu - 1.0);
328 let ln_b1 = ln_beta(0.5, nu - 0.5);
329 let ln_b2 = ln_beta(0.5, nu / 2.0);
330 let term3 = 2.0 * nu.sqrt() * (ln_b1 - 2.0 * ln_b2).exp() / (nu - 1.0);
331 let crps_std = term1 + term2 - term3;
332
333 d_params[[i, 1]] = sigma * crps_std + (y_i - mu) * d_params[[i, 0]];
334
335 let nu_plus = nu * (1.0 + eps);
337 let nu_minus = nu * (1.0 - eps);
338
339 let crps_plus = compute_t_crps_std(z, nu_plus);
340 let crps_minus = compute_t_crps_std(z, nu_minus);
341
342 d_params[[i, 2]] = sigma * nu * (crps_plus - crps_minus) / (2.0 * eps * nu);
343 }
344 }
345
346 d_params
347 }
348
349 fn metric(&self) -> Array3<f64> {
350 let n_obs = self.loc.len();
352 let n_params = 3;
353
354 let mut fi = Array3::zeros((n_obs, n_params, n_params));
355 for i in 0..n_obs {
356 for j in 0..n_params {
357 fi[[i, j, j]] = 1.0;
358 }
359 }
360 fi
361 }
362}
363
364fn compute_t_crps_std(z: f64, nu: f64) -> f64 {
366 if nu <= 1.0 {
367 return 1e10;
368 }
369
370 if let Ok(std_t) = StudentsTDist::new(0.0, 1.0, nu) {
371 let f_z = std_t.pdf(z);
372 let big_f_z = std_t.cdf(z);
373
374 let term1 = z * (2.0 * big_f_z - 1.0);
375 let term2 = 2.0 * f_z * (nu + z * z) / (nu - 1.0);
376 let ln_b1 = ln_beta(0.5, nu - 0.5);
377 let ln_b2 = ln_beta(0.5, nu / 2.0);
378 let term3 = 2.0 * nu.sqrt() * (ln_b1 - 2.0 * ln_b2).exp() / (nu - 1.0);
379
380 term1 + term2 - term3
381 } else {
382 1e10
383 }
384}
385
386#[derive(Debug, Clone)]
391pub struct TFixedDf {
392 pub loc: Array1<f64>,
394 pub scale: Array1<f64>,
396 pub var: Array1<f64>,
398 pub df: Array1<f64>,
400 pub fixed_df: f64,
402 _params: Array2<f64>,
404}
405
406impl TFixedDf {
407 pub const DEFAULT_FIXED_DF: f64 = 3.0;
409
410 pub fn from_params_with_df(params: &Array2<f64>, fixed_df: f64) -> Self {
412 let loc = params.column(0).to_owned();
413 let scale = params.column(1).mapv(f64::exp);
414 let var = &scale * &scale;
415 let n = loc.len();
416 let df = Array1::from_elem(n, fixed_df);
417 TFixedDf {
418 loc,
419 scale,
420 var,
421 df,
422 fixed_df,
423 _params: params.clone(),
424 }
425 }
426}
427
428impl Distribution for TFixedDf {
429 fn from_params(params: &Array2<f64>) -> Self {
430 Self::from_params_with_df(params, Self::DEFAULT_FIXED_DF)
431 }
432
433 fn fit(y: &Array1<f64>) -> Array1<f64> {
434 let mean = y.mean().unwrap_or(0.0);
435 let std_dev = y.std(0.0).max(1e-6);
436 array![mean, std_dev.ln()]
437 }
438
439 fn n_params(&self) -> usize {
440 2
441 }
442
443 fn predict(&self) -> Array1<f64> {
444 self.loc.clone()
445 }
446
447 fn params(&self) -> &Array2<f64> {
448 &self._params
449 }
450}
451
452impl RegressionDistn for TFixedDf {}
453
454impl DistributionMethods for TFixedDf {
455 fn mean(&self) -> Array1<f64> {
456 self.loc.clone()
458 }
459
460 fn variance(&self) -> Array1<f64> {
461 let mut result = Array1::zeros(self.loc.len());
463 for i in 0..self.loc.len() {
464 if self.df[i] > 2.0 {
465 result[i] = self.var[i] * self.df[i] / (self.df[i] - 2.0);
466 } else {
467 result[i] = f64::INFINITY;
468 }
469 }
470 result
471 }
472
473 fn pdf(&self, y: &Array1<f64>) -> Array1<f64> {
474 let mut result = Array1::zeros(y.len());
475 for i in 0..y.len() {
476 if let Ok(d) = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]) {
477 result[i] = d.pdf(y[i]);
478 }
479 }
480 result
481 }
482
483 fn cdf(&self, y: &Array1<f64>) -> Array1<f64> {
484 let mut result = Array1::zeros(y.len());
485 for i in 0..y.len() {
486 if let Ok(d) = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]) {
487 result[i] = d.cdf(y[i]);
488 }
489 }
490 result
491 }
492
493 fn ppf(&self, q: &Array1<f64>) -> Array1<f64> {
494 let mut result = Array1::zeros(q.len());
495 for i in 0..q.len() {
496 if let Ok(d) = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]) {
497 let q_clamped = q[i].clamp(1e-15, 1.0 - 1e-15);
498 result[i] = d.inverse_cdf(q_clamped);
499 }
500 }
501 result
502 }
503
504 fn sample(&self, n_samples: usize) -> Array2<f64> {
505 let n_obs = self.loc.len();
506 let mut samples = Array2::zeros((n_samples, n_obs));
507 let mut rng = rand::rng();
508
509 for i in 0..n_obs {
510 if let Ok(d) = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]) {
511 for s in 0..n_samples {
512 let u: f64 = rng.random();
513 samples[[s, i]] = d.inverse_cdf(u);
514 }
515 }
516 }
517 samples
518 }
519
520 fn median(&self) -> Array1<f64> {
521 self.loc.clone()
522 }
523
524 fn mode(&self) -> Array1<f64> {
525 self.loc.clone()
526 }
527}
528
529impl Scorable<LogScore> for TFixedDf {
530 fn score(&self, y: &Array1<f64>) -> Array1<f64> {
531 let mut scores = Array1::zeros(y.len());
532 for (i, &y_i) in y.iter().enumerate() {
533 let d = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]).unwrap();
534 scores[i] = -d.ln_pdf(y_i);
535 }
536 scores
537 }
538
539 fn d_score(&self, y: &Array1<f64>) -> Array2<f64> {
540 let n_obs = y.len();
541 let mut d_params = Array2::zeros((n_obs, 2));
542
543 for i in 0..n_obs {
544 let loc_i = self.loc[i];
545 let var_i = self.var[i];
546 let df_i = self.df[i];
547 let y_i = y[i];
548
549 let diff = y_i - loc_i;
550 let diff_sq = diff * diff;
551 let denom = df_i * var_i + diff_sq;
552
553 d_params[[i, 0]] = -(df_i + 1.0) * diff / denom;
555
556 d_params[[i, 1]] = 1.0 - (df_i + 1.0) * diff_sq / denom;
558 }
559
560 d_params
561 }
562
563 fn metric(&self) -> Array3<f64> {
564 let n_obs = self.loc.len();
566 let mut fi = Array3::zeros((n_obs, 2, 2));
567
568 for i in 0..n_obs {
569 let df_i = self.df[i];
570 let var_i = self.var[i];
571
572 fi[[i, 0, 0]] = (df_i + 1.0) / ((df_i + 3.0) * var_i);
574
575 fi[[i, 1, 1]] = df_i / (2.0 * (df_i + 3.0) * var_i);
579 }
580
581 fi
582 }
583}
584
585impl Scorable<CRPScore> for TFixedDf {
586 fn score(&self, y: &Array1<f64>) -> Array1<f64> {
587 let mut scores = Array1::zeros(y.len());
589
590 for i in 0..y.len() {
591 let mu = self.loc[i];
592 let sigma = self.scale[i];
593 let nu = self.df[i];
594 let y_i = y[i];
595
596 if nu <= 1.0 {
597 scores[i] = 1e10;
598 continue;
599 }
600
601 let z = (y_i - mu) / sigma;
602 scores[i] = sigma * compute_t_crps_std(z, nu);
603 }
604 scores
605 }
606
607 fn d_score(&self, y: &Array1<f64>) -> Array2<f64> {
608 let n_obs = y.len();
610 let mut d_params = Array2::zeros((n_obs, 2));
611
612 for i in 0..n_obs {
613 let mu = self.loc[i];
614 let sigma = self.scale[i];
615 let nu = self.df[i];
616 let y_i = y[i];
617
618 if nu <= 1.0 {
619 continue;
620 }
621
622 let z = (y_i - mu) / sigma;
623
624 if let Ok(std_t) = StudentsTDist::new(0.0, 1.0, nu) {
625 let big_f_z = std_t.cdf(z);
626
627 d_params[[i, 0]] = -(2.0 * big_f_z - 1.0);
629
630 let crps_std = compute_t_crps_std(z, nu);
632 d_params[[i, 1]] = sigma * crps_std + (y_i - mu) * d_params[[i, 0]];
633 }
634 }
635
636 d_params
637 }
638
639 fn metric(&self) -> Array3<f64> {
640 let n_obs = self.loc.len();
642 let mut fi = Array3::zeros((n_obs, 2, 2));
643
644 for i in 0..n_obs {
645 let df_i = self.df[i];
646 let var_i = self.var[i];
647 fi[[i, 0, 0]] = (df_i + 1.0) / ((df_i + 3.0) * var_i);
648 fi[[i, 1, 1]] = df_i / (2.0 * (df_i + 3.0) * var_i);
649 }
650
651 fi
652 }
653}
654
655#[derive(Debug, Clone)]
659pub struct TFixedDfFixedVar {
660 pub loc: Array1<f64>,
662 pub scale: Array1<f64>,
664 pub var: Array1<f64>,
666 pub df: Array1<f64>,
668 pub fixed_df: f64,
670 _params: Array2<f64>,
672}
673
674impl TFixedDfFixedVar {
675 pub const DEFAULT_FIXED_DF: f64 = 3.0;
677
678 pub fn from_params_with_df(params: &Array2<f64>, fixed_df: f64) -> Self {
680 let loc = params.column(0).to_owned();
681 let n = loc.len();
682 let scale = Array1::ones(n);
683 let var = Array1::ones(n);
684 let df = Array1::from_elem(n, fixed_df);
685 TFixedDfFixedVar {
686 loc,
687 scale,
688 var,
689 df,
690 fixed_df,
691 _params: params.clone(),
692 }
693 }
694}
695
696impl Distribution for TFixedDfFixedVar {
697 fn from_params(params: &Array2<f64>) -> Self {
698 Self::from_params_with_df(params, Self::DEFAULT_FIXED_DF)
699 }
700
701 fn fit(y: &Array1<f64>) -> Array1<f64> {
702 let mean = y.mean().unwrap_or(0.0);
703 array![mean]
704 }
705
706 fn n_params(&self) -> usize {
707 1
708 }
709
710 fn predict(&self) -> Array1<f64> {
711 self.loc.clone()
712 }
713
714 fn params(&self) -> &Array2<f64> {
715 &self._params
716 }
717}
718
719impl RegressionDistn for TFixedDfFixedVar {}
720
721impl DistributionMethods for TFixedDfFixedVar {
722 fn mean(&self) -> Array1<f64> {
723 self.loc.clone()
724 }
725
726 fn variance(&self) -> Array1<f64> {
727 let mut result = Array1::zeros(self.loc.len());
729 for i in 0..self.loc.len() {
730 result[i] = self.var[i] * self.df[i] / (self.df[i] - 2.0);
731 }
732 result
733 }
734
735 fn pdf(&self, y: &Array1<f64>) -> Array1<f64> {
736 let mut result = Array1::zeros(y.len());
737 for i in 0..y.len() {
738 if let Ok(d) = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]) {
739 result[i] = d.pdf(y[i]);
740 }
741 }
742 result
743 }
744
745 fn cdf(&self, y: &Array1<f64>) -> Array1<f64> {
746 let mut result = Array1::zeros(y.len());
747 for i in 0..y.len() {
748 if let Ok(d) = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]) {
749 result[i] = d.cdf(y[i]);
750 }
751 }
752 result
753 }
754
755 fn ppf(&self, q: &Array1<f64>) -> Array1<f64> {
756 let mut result = Array1::zeros(q.len());
757 for i in 0..q.len() {
758 if let Ok(d) = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]) {
759 let q_clamped = q[i].clamp(1e-15, 1.0 - 1e-15);
760 result[i] = d.inverse_cdf(q_clamped);
761 }
762 }
763 result
764 }
765
766 fn sample(&self, n_samples: usize) -> Array2<f64> {
767 let n_obs = self.loc.len();
768 let mut samples = Array2::zeros((n_samples, n_obs));
769 let mut rng = rand::rng();
770
771 for i in 0..n_obs {
772 if let Ok(d) = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]) {
773 for s in 0..n_samples {
774 let u: f64 = rng.random();
775 samples[[s, i]] = d.inverse_cdf(u);
776 }
777 }
778 }
779 samples
780 }
781
782 fn median(&self) -> Array1<f64> {
783 self.loc.clone()
784 }
785
786 fn mode(&self) -> Array1<f64> {
787 self.loc.clone()
788 }
789}
790
791impl Scorable<LogScore> for TFixedDfFixedVar {
792 fn score(&self, y: &Array1<f64>) -> Array1<f64> {
793 let mut scores = Array1::zeros(y.len());
794 for (i, &y_i) in y.iter().enumerate() {
795 let d = StudentsTDist::new(self.loc[i], self.scale[i], self.df[i]).unwrap();
796 scores[i] = -d.ln_pdf(y_i);
797 }
798 scores
799 }
800
801 fn d_score(&self, y: &Array1<f64>) -> Array2<f64> {
802 let n_obs = y.len();
803 let mut d_params = Array2::zeros((n_obs, 1));
804
805 for i in 0..n_obs {
806 let loc_i = self.loc[i];
807 let var_i = self.var[i];
808 let df_i = self.df[i];
809 let y_i = y[i];
810
811 let diff = y_i - loc_i;
812 let diff_sq = diff * diff;
813
814 let num = (df_i + 1.0) * (2.0 / (df_i * var_i)) * diff;
816 let den = 2.0 * (1.0 + (1.0 / (df_i * var_i)) * diff_sq);
817 d_params[[i, 0]] = -num / den;
818 }
819
820 d_params
821 }
822
823 fn metric(&self) -> Array3<f64> {
824 let n_obs = self.loc.len();
825 let mut fi = Array3::zeros((n_obs, 1, 1));
826
827 for i in 0..n_obs {
828 let df_i = self.df[i];
829 let var_i = self.var[i];
830 fi[[i, 0, 0]] = (df_i + 1.0) / ((df_i + 3.0) * var_i);
831 }
832
833 fi
834 }
835}
836
837#[cfg(test)]
838mod tests {
839 use super::*;
840
841 #[test]
842 fn test_studentt_crpscore() {
843 let params = Array2::from_shape_vec((1, 3), vec![0.0, 0.0, 5.0_f64.ln()]).unwrap();
845 let dist = StudentT::from_params(¶ms);
846
847 let y = Array1::from_vec(vec![0.0]);
848 let score = Scorable::<CRPScore>::score(&dist, &y);
849
850 assert!(score[0].is_finite());
852 assert!(score[0] >= 0.0);
853 }
854
855 #[test]
856 fn test_studentt_crpscore_at_mean() {
857 let params = Array2::from_shape_vec((1, 3), vec![5.0, 0.0, 3.0_f64.ln()]).unwrap();
859 let dist = StudentT::from_params(¶ms);
860
861 let y = Array1::from_vec(vec![5.0]); let score = Scorable::<CRPScore>::score(&dist, &y);
863
864 assert!(score[0].is_finite());
865 assert!(score[0] >= 0.0);
866 assert!(score[0] < 1.0); }
868
869 #[test]
870 fn test_studentt_crpscore_df_1_penalty() {
871 let params = Array2::from_shape_vec((1, 3), vec![0.0, 0.0, 0.0]).unwrap(); let dist = StudentT::from_params(¶ms);
874
875 let y = Array1::from_vec(vec![0.0]);
876 let score = Scorable::<CRPScore>::score(&dist, &y);
877
878 assert!(score[0] > 1e9);
880 }
881
882 #[test]
883 fn test_studentt_crpscore_d_score() {
884 let params = Array2::from_shape_vec((1, 3), vec![0.0, 0.0, 3.0_f64.ln()]).unwrap();
885 let dist = StudentT::from_params(¶ms);
886
887 let y = Array1::from_vec(vec![1.0]);
888 let d_score = Scorable::<CRPScore>::d_score(&dist, &y);
889
890 assert!(d_score[[0, 0]].is_finite());
892 assert!(d_score[[0, 1]].is_finite());
893 assert!(d_score[[0, 2]].is_finite());
894 }
895
896 #[test]
897 fn test_tfixeddf_crpscore() {
898 let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
900 let dist = TFixedDf::from_params(¶ms);
901
902 let y = Array1::from_vec(vec![0.0]);
903 let score = Scorable::<CRPScore>::score(&dist, &y);
904
905 assert!(score[0].is_finite());
906 assert!(score[0] >= 0.0);
907 }
908
909 #[test]
910 fn test_tfixeddf_crpscore_d_score() {
911 let params = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
912 let dist = TFixedDf::from_params(¶ms);
913
914 let y = Array1::from_vec(vec![1.0]);
915 let d_score = Scorable::<CRPScore>::d_score(&dist, &y);
916
917 assert!(d_score[[0, 0]].is_finite());
918 assert!(d_score[[0, 1]].is_finite());
919 }
920
921 #[test]
922 fn test_tfixeddfixedvar_crpscore() {
923 let params = Array2::from_shape_vec((1, 1), vec![0.0]).unwrap();
925 let dist = TFixedDfFixedVar::from_params(¶ms);
926
927 let y = Array1::from_vec(vec![0.0]);
928 let score = Scorable::<CRPScore>::score(&dist, &y);
929
930 assert!(score[0].is_finite());
931 assert!(score[0] >= 0.0);
932 }
933
934 #[test]
935 fn test_tfixeddfixedvar_crpscore_d_score() {
936 let params = Array2::from_shape_vec((1, 1), vec![0.0]).unwrap();
937 let dist = TFixedDfFixedVar::from_params(¶ms);
938
939 let y = Array1::from_vec(vec![1.0]);
940 let d_score = Scorable::<CRPScore>::d_score(&dist, &y);
941
942 assert!(d_score[[0, 0]].is_finite());
943 }
944
945 #[test]
946 fn test_studentt_crps_converges_to_normal() {
947 let params_t = Array2::from_shape_vec((1, 3), vec![0.0, 0.0, 100.0_f64.ln()]).unwrap();
950 let dist_t = StudentT::from_params(¶ms_t);
951
952 let y = Array1::from_vec(vec![1.0]);
953 let score_t = Scorable::<CRPScore>::score(&dist_t, &y);
954
955 use crate::dist::Normal;
957 let params_n = Array2::from_shape_vec((1, 2), vec![0.0, 0.0]).unwrap();
958 let dist_n = Normal::from_params(¶ms_n);
959 let score_n = Scorable::<CRPScore>::score(&dist_n, &y);
960
961 assert!(score_t[0].is_finite());
963 assert!((score_t[0] - score_n[0]).abs() < 0.01);
964 }
965}
966
967impl Scorable<CRPScore> for TFixedDfFixedVar {
968 fn score(&self, y: &Array1<f64>) -> Array1<f64> {
969 let mut scores = Array1::zeros(y.len());
971
972 for i in 0..y.len() {
973 let mu = self.loc[i];
974 let sigma = self.scale[i]; let nu = self.df[i];
976 let y_i = y[i];
977
978 if nu <= 1.0 {
979 scores[i] = 1e10;
980 continue;
981 }
982
983 let z = (y_i - mu) / sigma;
984 scores[i] = sigma * compute_t_crps_std(z, nu);
985 }
986 scores
987 }
988
989 fn d_score(&self, y: &Array1<f64>) -> Array2<f64> {
990 let n_obs = y.len();
992 let mut d_params = Array2::zeros((n_obs, 1));
993
994 for i in 0..n_obs {
995 let mu = self.loc[i];
996 let sigma = self.scale[i];
997 let nu = self.df[i];
998 let y_i = y[i];
999
1000 if nu <= 1.0 {
1001 continue;
1002 }
1003
1004 let z = (y_i - mu) / sigma;
1005
1006 if let Ok(std_t) = StudentsTDist::new(0.0, 1.0, nu) {
1007 let big_f_z = std_t.cdf(z);
1008 d_params[[i, 0]] = -(2.0 * big_f_z - 1.0);
1010 }
1011 }
1012
1013 d_params
1014 }
1015
1016 fn metric(&self) -> Array3<f64> {
1017 let n_obs = self.loc.len();
1018 let mut fi = Array3::zeros((n_obs, 1, 1));
1019
1020 for i in 0..n_obs {
1021 let df_i = self.df[i];
1022 let var_i = self.var[i];
1023 fi[[i, 0, 0]] = (df_i + 1.0) / ((df_i + 3.0) * var_i);
1024 }
1025
1026 fi
1027 }
1028}