pub fn eigh(a: &[f64], n: usize) -> (Vec<f64>, Vec<f64>) {
let mut am = a.to_vec();
let mut w = vec![0f64; n];
let _info = crate::blas::dsyevd(&mut am, &mut w, n);
(w, am)
}
pub fn eigh_batch(mats: &[Vec<f64>], n: usize) -> Vec<(Vec<f64>, Vec<f64>)> {
use rayon::prelude::*;
mats.par_iter().map(|a| eigh(a, n)).collect()
}
pub fn matrix_fn(a: &[f64], n: usize, f: impl Fn(f64) -> f64) -> Vec<f64> {
let (w, v) = eigh(a, n);
let fl: Vec<f64> = w.iter().map(|&l| f(l)).collect();
let mut out = vec![0f64; n * n];
for k in 0..n {
let fk = fl[k];
for i in 0..n {
let vik = fk * v[k * n + i];
for j in 0..n {
out[i * n + j] += vik * v[k * n + j];
}
}
}
out
}
pub fn logm(a: &[f64], n: usize) -> Vec<f64> {
matrix_fn(a, n, |l| l.max(1e-12).ln())
}
pub fn expm(a: &[f64], n: usize) -> Vec<f64> {
matrix_fn(a, n, |l| l.exp())
}
pub fn sqrtm(a: &[f64], n: usize) -> Vec<f64> {
matrix_fn(a, n, |l| l.max(0.0).sqrt())
}
pub fn invsqrtm(a: &[f64], n: usize) -> Vec<f64> {
matrix_fn(a, n, |l| 1.0 / l.max(1e-12).sqrt())
}
fn matmul(a: &[f64], b: &[f64], n: usize) -> Vec<f64> {
let mut o = vec![0f64; n * n];
for i in 0..n {
for k in 0..n {
let aik = a[i * n + k];
if aik == 0.0 {
continue;
}
for j in 0..n {
o[i * n + j] += aik * b[k * n + j];
}
}
}
o
}
pub fn airm_dist2(a: &[f64], b: &[f64], n: usize) -> f64 {
let w = invsqrtm(a, n);
let m = matmul(&matmul(&w, b, n), &w, n);
let (evals, _) = eigh(&m, n);
evals.iter().map(|&l| l.max(1e-12).ln().powi(2)).sum()
}
pub fn karcher_mean(covs: &[Vec<f64>], n: usize, iters: usize, tol: f64) -> Vec<f64> {
let k = covs.len().max(1) as f64;
let mut m = vec![0f64; n * n];
for c in covs {
for i in 0..n * n {
m[i] += c[i] / k;
}
}
for _ in 0..iters {
let msqrt = sqrtm(&m, n);
let minv = invsqrtm(&m, n);
let mut sbar = vec![0f64; n * n];
for c in covs {
let wcw = matmul(&matmul(&minv, c, n), &minv, n);
let l = logm(&wcw, n);
for i in 0..n * n {
sbar[i] += l[i] / k;
}
}
let e = expm(&sbar, n);
m = matmul(&matmul(&msqrt, &e, n), &msqrt, n);
let norm: f64 = sbar.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm < tol {
break;
}
}
m
}
#[cfg(test)]
mod tests {
use super::*;
fn ident(n: usize) -> Vec<f64> {
let mut a = vec![0f64; n * n];
for i in 0..n {
a[i * n + i] = 1.0;
}
a
}
#[test]
fn logm_expm_roundtrip() {
let n = 4;
let mut a = vec![0f64; n * n];
for i in 0..n {
for j in 0..n {
a[i * n + j] = if i == j { 2.0 + i as f64 } else { 0.3 };
}
}
let back = expm(&logm(&a, n), n);
let err: f64 = a
.iter()
.zip(&back)
.map(|(x, y)| (x - y).abs())
.fold(0.0, f64::max);
assert!(err < 1e-6, "logm/expm roundtrip err {err}");
}
#[test]
fn sqrtm_squares_back() {
let n = 3;
let a = vec![4.0, 0.0, 0.0, 0.0, 9.0, 0.0, 0.0, 0.0, 16.0];
let s = sqrtm(&a, n);
assert!(
(s[0] - 2.0).abs() < 1e-9 && (s[4] - 3.0).abs() < 1e-9 && (s[8] - 4.0).abs() < 1e-9
);
}
#[test]
fn airm_dist_zero_to_self_and_invariant() {
let n = 3;
let a = vec![2.0, 0.1, 0.0, 0.1, 3.0, 0.2, 0.0, 0.2, 1.5];
assert!(airm_dist2(&a, &a, n) < 1e-9, "distance to self must be 0");
let k = 5.0;
let ka: Vec<f64> = a.iter().map(|x| x * k).collect();
let ki: Vec<f64> = ident(n).iter().map(|x| x * k).collect();
let d1 = airm_dist2(&a, &ident(n), n);
let d2 = airm_dist2(&ka, &ki, n);
assert!(
(d1 - d2).abs() < 1e-6,
"AIRM not affine-invariant: {d1} vs {d2}"
);
}
#[test]
fn karcher_mean_of_identicals_is_the_matrix() {
let n = 3;
let a = vec![2.0, 0.1, 0.0, 0.1, 3.0, 0.2, 0.0, 0.2, 1.5];
let m = karcher_mean(&[a.clone(), a.clone(), a.clone()], n, 20, 1e-10);
let err: f64 = a
.iter()
.zip(&m)
.map(|(x, y)| (x - y).abs())
.fold(0.0, f64::max);
assert!(err < 1e-6, "Karcher mean of identicals err {err}");
}
}