use crate::error::{Error, Result};
use crate::reconciliation::{reconcile, ReconciliationMethod, SummingMatrix};
use faer::Mat;
pub struct HierarchicalConformal {
s: SummingMatrix,
method: ReconciliationMethod,
scores: Vec<f64>,
quantile: f64,
}
impl HierarchicalConformal {
pub fn new(s: SummingMatrix, method: ReconciliationMethod) -> Self {
Self {
s,
method,
scores: Vec::new(),
quantile: 0.0,
}
}
pub fn calibrate(
&mut self,
y_calib: &Mat<f64>,
y_hat_calib: &Mat<f64>,
alpha: f64,
) -> Result<()> {
let n_calib = y_calib.ncols();
if y_hat_calib.ncols() != n_calib {
return Err(Error::ShapeMismatch {
expected: format!("{} columns", n_calib),
actual: format!("{} columns", y_hat_calib.ncols()),
});
}
let y_tilde_calib = reconcile(&self.s, y_hat_calib, self.method.clone())?;
let mut scores = Vec::with_capacity(n_calib);
for j in 0..n_calib {
let mut score: f64 = 0.0;
for i in 0..y_calib.nrows() {
let diff = y_calib[(i, j)] - y_tilde_calib[(i, j)];
score += diff.powi(2);
}
scores.push(score.sqrt());
}
scores.sort_by(|a, b| a.total_cmp(b));
let q_idx = (((n_calib + 1) as f64 * (1.0 - alpha)).ceil() as usize).min(n_calib) - 1;
self.quantile = scores[q_idx];
self.scores = scores;
Ok(())
}
pub fn predict(&self, y_hat: &Mat<f64>) -> Result<Mat<f64>> {
reconcile(&self.s, y_hat, self.method.clone())
}
pub fn quantile(&self) -> f64 {
self.quantile
}
pub fn predict_intervals(&self, y_hat: &Mat<f64>) -> Result<(Mat<f64>, Mat<f64>)> {
let y_tilde = self.predict(y_hat)?;
let mut lower = y_tilde.clone();
let mut upper = y_tilde.clone();
for j in 0..y_tilde.ncols() {
for i in 0..y_tilde.nrows() {
lower[(i, j)] -= self.quantile;
upper[(i, j)] += self.quantile;
}
}
Ok((lower, upper))
}
}