use blr_core::{features::polynomial, fit, ArdConfig};
#[test]
fn test_epistemic_increases_far_from_data() {
let n = 50usize;
let x_train: Vec<f64> = (0..n)
.map(|i| -2.0 + 4.0 * i as f64 / (n as f64 - 1.0))
.collect();
let y_train: Vec<f64> = x_train
.iter()
.enumerate()
.map(|(i, xi)| xi * 2.0 + ((i as f64 * 1.5).sin() * 0.1))
.collect();
let (phi_train, d) = polynomial(&x_train, 2);
let cfg = ArdConfig {
max_iter: 300,
tol: 1e-6,
..Default::default()
};
let model = fit(&phi_train, &y_train, n, d, &cfg).expect("fit failed");
let x_inside = vec![0.0_f64];
let x_outside = vec![5.0_f64];
let (phi_inside, _) = polynomial(&x_inside, 2);
let (phi_outside, _) = polynomial(&x_outside, 2);
let pred_inside = model.predict(&phi_inside, 1, d);
let pred_outside = model.predict(&phi_outside, 1, d);
let ep_inside = pred_inside.epistemic_std[0];
let ep_outside = pred_outside.epistemic_std[0];
assert!(
ep_outside >= 2.0 * ep_inside,
"epistemic_std at x=5 ({ep_outside:.4}) must be >= 2x inside ({ep_inside:.4})"
);
}
#[test]
fn test_joint_predictive_psd() {
let n = 30usize;
let x_train: Vec<f64> = (0..n).map(|i| i as f64 * 0.2 - 3.0).collect();
let y_train: Vec<f64> = x_train.iter().map(|xi| xi.sin() + 0.1 * xi).collect();
let (phi_train, d) = polynomial(&x_train, 3);
let cfg = ArdConfig {
max_iter: 200,
tol: 1e-6,
..Default::default()
};
let model = fit(&phi_train, &y_train, n, d, &cfg).expect("fit failed");
let x_test = vec![-1.0, -0.5, 0.0, 0.5, 1.0_f64];
let m = x_test.len();
let (phi_test, _) = polynomial(&x_test, 3);
let joint = model
.predict_gaussian(&phi_test, m, d)
.expect("predict_gaussian failed");
assert_eq!(joint.mean.len(), m);
assert_eq!(joint.cov.len(), m * m);
for i in 0..m {
let cov_ii = joint.cov[i * m + i];
assert!(
cov_ii > -1e-9,
"cov[{i},{i}] = {cov_ii:.6e} — diagonal should be non-negative"
);
}
for i in 0..m {
for j in 0..m {
let diff = (joint.cov[i * m + j] - joint.cov[j * m + i]).abs();
assert!(
diff < 1e-12,
"cov not symmetric at ({i},{j}): diff={diff:.2e}"
);
}
}
}
#[test]
fn test_predictive_marginals_fields() {
let n = 20usize;
let x_train: Vec<f64> = (0..n).map(|i| i as f64 * 0.1).collect();
let y_train: Vec<f64> = x_train.iter().map(|xi| xi * 3.0 + 1.0).collect();
let (phi_train, d) = polynomial(&x_train, 1);
let model = fit(&phi_train, &y_train, n, d, &ArdConfig::default()).expect("fit failed");
let x_test = vec![0.5, 1.0, 2.0_f64];
let m = x_test.len();
let (phi_test, _) = polynomial(&x_test, 1);
let pred = model.predict(&phi_test, m, d);
assert_eq!(pred.mean.len(), m);
assert_eq!(pred.epistemic_std.len(), m);
assert_eq!(pred.total_std.len(), m);
assert!(pred.aleatoric_std > 0.0);
for i in 0..m {
assert!(
pred.total_std[i] >= pred.aleatoric_std,
"total_std[{i}]={} < aleatoric_std={}",
pred.total_std[i],
pred.aleatoric_std
);
}
}