pub fn posterior_variance(
beta: f64,
sigma_cov: &[f64],
d: usize,
phi_test: &[f64],
n_test: usize,
) -> Vec<f64> {
debug_assert_eq!(sigma_cov.len(), d * d, "sigma_cov must be D×D row-major");
debug_assert_eq!(
phi_test.len(),
n_test * d,
"phi_test must be N_test×D row-major"
);
let noise_var = 1.0 / beta.max(1e-10); let mut variances = vec![noise_var; n_test];
for i in 0..n_test {
let phi_i = &phi_test[i * d..(i + 1) * d];
let mut sigma_phi = vec![0.0_f64; d];
for row in 0..d {
let mut acc = 0.0_f64;
for col in 0..d {
acc += sigma_cov[row * d + col] * phi_i[col];
}
sigma_phi[row] = acc;
}
let epistemic: f64 = phi_i.iter().zip(sigma_phi.iter()).map(|(a, b)| a * b).sum();
variances[i] = noise_var + epistemic.max(0.0);
}
variances
}
pub fn posterior_std(
beta: f64,
sigma_cov: &[f64],
d: usize,
phi_test: &[f64],
n_test: usize,
) -> Vec<f64> {
posterior_variance(beta, sigma_cov, d, phi_test, n_test)
.into_iter()
.map(f64::sqrt)
.collect()
}
pub fn posterior_std_grid(
beta: f64,
sigma_cov: &[f64],
d: usize,
input_min: f64,
input_max: f64,
resolution: usize,
feature_fn: &dyn Fn(f64) -> Vec<f64>,
) -> (Vec<f64>, Vec<f64>) {
let resolution = resolution.max(2);
let step = (input_max - input_min) / (resolution - 1) as f64;
let grid: Vec<f64> = (0..resolution)
.map(|k| input_min + k as f64 * step)
.collect();
let mut phi_grid = Vec::with_capacity(resolution * d);
for &x in &grid {
let feats = feature_fn(x);
let actual_d = feats.len().min(d);
phi_grid.extend_from_slice(&feats[..actual_d]);
if actual_d < d {
phi_grid.extend(std::iter::repeat_n(0.0, d - actual_d));
}
}
let stds = posterior_std(beta, sigma_cov, d, &phi_grid, resolution);
(grid, stds)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_variance_identity_cov() {
let d = 2usize;
let sigma_cov = vec![1.0, 0.0, 0.0, 1.0]; let beta = 1.0;
let phi_test = vec![1.0, 0.0];
let var = posterior_variance(beta, &sigma_cov, d, &phi_test, 1);
assert!((var[0] - 2.0).abs() < 1e-10, "expected 2.0, got {}", var[0]);
}
#[test]
fn test_variance_grid_nonnegative() {
let d = 3usize;
let sigma_cov = vec![0.1, 0.0, 0.0, 0.0, 0.2, 0.0, 0.0, 0.0, 0.05];
let beta = 2.0;
let feature_fn = |x: f64| vec![x, x * x, x.sin()];
let (_, stds) = posterior_std_grid(beta, &sigma_cov, d, -5.0, 5.0, 50, &feature_fn);
for s in &stds {
assert!(
s.is_finite() && *s >= 0.0,
"non-finite or negative std: {s}"
);
}
}
#[test]
fn test_variance_deterministic() {
let d = 2usize;
let sigma_cov = vec![0.5, 0.1, 0.1, 0.3];
let beta = 1.5;
let phi_test = vec![1.0, -0.5, 0.0, 1.0];
let var1 = posterior_variance(beta, &sigma_cov, d, &phi_test, 2);
let var2 = posterior_variance(beta, &sigma_cov, d, &phi_test, 2);
for (a, b) in var1.iter().zip(var2.iter()) {
assert_eq!(a, b, "result not deterministic");
}
}
#[test]
fn test_variance_near_singular() {
let d = 3usize;
let eps = 1e-9;
let sigma_cov = vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, eps];
let beta = 1.0;
let phi_test = vec![0.0, 0.0, 100.0]; let var = posterior_variance(beta, &sigma_cov, d, &phi_test, 1);
assert!(var[0].is_finite(), "variance must be finite: {}", var[0]);
assert!(var[0] >= 0.0, "variance must be non-negative: {}", var[0]);
}
#[test]
fn test_variance_zero_beta() {
let d = 2usize;
let sigma_cov = vec![1.0, 0.0, 0.0, 1.0];
let var = posterior_variance(0.0, &sigma_cov, d, &[0.5, 0.5], 1);
assert!(var[0].is_finite(), "variance must be finite even with β=0");
}
}