use tang_la::{DMat, SymmetricEigen};
fn fmat(a: &[f64], m: usize, n: usize) -> faer::MatRef<'_, f64> {
faer::MatRef::from_row_major_slice(a, m, n)
}
fn gemm(lhs: faer::MatRef<'_, f64>, rhs: faer::MatRef<'_, f64>) -> Vec<f64> {
let (m, n) = (lhs.nrows(), rhs.ncols());
let mut c = vec![0.0; m * n];
if m == 0 || n == 0 || lhs.ncols() == 0 {
return c;
}
faer::linalg::matmul::matmul(
faer::MatMut::from_row_major_slice_mut(&mut c, m, n),
faer::Accum::Replace,
lhs,
rhs,
1.0,
faer::Par::Seq,
);
c
}
pub fn gram_rows(a: &[f64], m: usize, n: usize) -> Vec<f64> {
let mut g = gemm(fmat(a, m, n), fmat(a, m, n).transpose());
symmetrize(&mut g, m);
g
}
pub fn gram_cols(a: &[f64], m: usize, n: usize) -> Vec<f64> {
let mut g = gemm(fmat(a, m, n).transpose(), fmat(a, m, n));
symmetrize(&mut g, n);
g
}
fn symmetrize(g: &mut [f64], n: usize) {
for i in 0..n {
for j in i + 1..n {
let v = 0.5 * (g[i * n + j] + g[j * n + i]);
g[i * n + j] = v;
g[j * n + i] = v;
}
}
}
pub fn matmul(a: &[f64], b: &[f64], m: usize, k: usize, n: usize) -> Vec<f64> {
gemm(fmat(a, m, k), fmat(b, k, n))
}
pub fn matmul_tn(a: &[f64], b: &[f64], k: usize, m: usize, n: usize) -> Vec<f64> {
gemm(fmat(a, k, m).transpose(), fmat(b, k, n))
}
pub fn kron_eye_apply(h: &[f64], r: usize, n: usize, x: &[f64], p: usize) -> Vec<f64> {
matmul(h, x, r, r, n * p)
}
pub fn sym_eig_desc(g: &[f64], n: usize) -> (Vec<f64>, Vec<f64>) {
use faer::dyn_stack::{MemBuffer, MemStack};
use faer::linalg::evd;
let a = faer::Mat::<f64>::from_fn(n, n, |i, j| g[i * n + j]);
let mut u = faer::Mat::<f64>::zeros(n, n);
let mut s = faer::diag::Diag::<f64>::zeros(n);
let par = faer::Par::Seq;
let scratch = evd::self_adjoint_evd_scratch::<f64>(
n,
evd::ComputeEigenvectors::Yes,
par,
Default::default(),
);
let ok = evd::self_adjoint_evd(
a.as_ref(),
s.as_mut(),
Some(u.as_mut()),
par,
MemStack::new(&mut MemBuffer::new(scratch)),
Default::default(),
);
if ok.is_err() {
return sym_eig_desc_tang(g, n);
}
let mut vals = Vec::with_capacity(n);
let mut vecs = vec![0.0; n * n];
for (jj, j) in (0..n).rev().enumerate() {
vals.push(s[j]);
for i in 0..n {
vecs[i * n + jj] = u[(i, j)];
}
}
(vals, vecs)
}
fn sym_eig_desc_tang(g: &[f64], n: usize) -> (Vec<f64>, Vec<f64>) {
let e = SymmetricEigen::new(&DMat::from_raw(n, n, g.to_vec()));
let mut vals = Vec::with_capacity(n);
let mut vecs = vec![0.0; n * n];
for (jj, j) in (0..n).rev().enumerate() {
vals.push(e.eigenvalues[j]);
for i in 0..n {
vecs[i * n + jj] = e.eigenvectors.get(i, j);
}
}
(vals, vecs)
}
pub fn cholesky(a: &[f64], n: usize) -> Option<Vec<f64>> {
let mut l = vec![0.0; n * n];
for i in 0..n {
for j in 0..=i {
let mut s = a[i * n + j];
for k in 0..j {
s -= l[i * n + k] * l[j * n + k];
}
if i == j {
if s <= 0.0 || !s.is_finite() {
return None;
}
l[i * n + i] = s.sqrt();
} else {
l[i * n + j] = s / l[j * n + j];
}
}
}
Some(l)
}
pub fn solve_lower(l: &[f64], b: &mut [f64], n: usize, m: usize) {
for i in 0..n {
for k in 0..i {
let lik = l[i * n + k];
if lik != 0.0 {
let (head, tail) = b.split_at_mut(i * m);
let src = &head[k * m..k * m + m];
for (t, s) in tail[..m].iter_mut().zip(src) {
*t -= lik * s;
}
}
}
let d = l[i * n + i];
for t in &mut b[i * m..i * m + m] {
*t /= d;
}
}
}
pub fn solve_lower_t(l: &[f64], b: &mut [f64], n: usize, m: usize) {
for i in (0..n).rev() {
for k in i + 1..n {
let lki = l[k * n + i];
if lki != 0.0 {
let (head, tail) = b.split_at_mut(k * m);
for (t, s) in head[i * m..i * m + m].iter_mut().zip(&tail[..m]) {
*t -= lki * s;
}
}
}
let d = l[i * n + i];
for t in &mut b[i * m..i * m + m] {
*t /= d;
}
}
}
pub fn transpose(a: &[f64], m: usize, n: usize) -> Vec<f64> {
let mut t = vec![0.0; m * n];
for i in 0..m {
for j in 0..n {
t[j * m + i] = a[i * n + j];
}
}
t
}
pub fn svd_left(
a: &[f64],
m: usize,
n: usize,
pick: impl FnOnce(&[f64]) -> usize,
) -> (Vec<f64>, Vec<f64>, usize) {
if m <= n {
let g = gram_rows(a, m, n);
let (lam, v) = sym_eig_desc(&g, m);
let s: Vec<f64> = lam.iter().map(|&l| l.max(0.0).sqrt()).collect();
let k = pick(&s).min(m);
let mut u = vec![0.0; m * k];
for i in 0..m {
u[i * k..i * k + k].copy_from_slice(&v[i * m..i * m + k]);
}
(s, u, k)
} else {
let g = gram_cols(a, m, n);
let (lam, v) = sym_eig_desc(&g, n);
let s: Vec<f64> = lam.iter().map(|&l| l.max(0.0).sqrt()).collect();
let smax = s.first().copied().unwrap_or(0.0);
let k = pick(&s).min(n).min(
s.iter()
.take_while(|&&x| x > 1e-13 * smax && x > 0.0)
.count(),
);
let mut vk = vec![0.0; n * k];
for i in 0..n {
vk[i * k..i * k + k].copy_from_slice(&v[i * n..i * n + k]);
}
let mut u = matmul(a, &vk, m, n, k);
for j in 0..k {
for i in 0..m {
u[i * k + j] /= s[j];
}
}
orthonormalize_cols(&mut u, m, k);
(s, u, k)
}
}
pub fn orthonormalize_cols(u: &mut [f64], m: usize, k: usize) {
for _pass in 0..2 {
for j in 0..k {
for p in 0..j {
let mut dot = 0.0;
for i in 0..m {
dot += u[i * k + j] * u[i * k + p];
}
for i in 0..m {
u[i * k + j] -= dot * u[i * k + p];
}
}
let nrm = (0..m)
.map(|i| u[i * k + j] * u[i * k + j])
.sum::<f64>()
.sqrt();
if nrm > 0.0 {
for i in 0..m {
u[i * k + j] /= nrm;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn svd_left_reconstructs_both_orientations() {
for (m, n) in [(4usize, 9usize), (9, 4)] {
let a: Vec<f64> = (0..m * n).map(|i| ((i * 37 % 11) as f64).cos()).collect();
let (s, u, k) = svd_left(&a, m, n, |s| s.len());
assert_eq!(k, m.min(n));
let uta = matmul_tn(&u, &a, m, k, n);
let back = matmul(&u, &uta, m, k, n);
for (x, y) in a.iter().zip(&back) {
assert!((x - y).abs() < 1e-9);
}
let fro: f64 = a.iter().map(|x| x * x).sum();
let ss: f64 = s.iter().map(|x| x * x).sum();
assert!((fro - ss).abs() < 1e-9 * fro);
}
}
#[test]
fn cholesky_solves() {
let a = [4.0, 2.0, 0.6, 2.0, 5.0, 1.0, 0.6, 1.0, 3.0];
let l = cholesky(&a, 3).unwrap();
let mut b = vec![1.0, 2.0, 3.0];
solve_lower(&l, &mut b, 3, 1);
solve_lower_t(&l, &mut b, 3, 1);
let ax: Vec<f64> = (0..3)
.map(|i| (0..3).map(|j| a[i * 3 + j] * b[j]).sum())
.collect();
for (x, y) in ax.iter().zip([1.0, 2.0, 3.0]) {
assert!((x - y).abs() < 1e-12);
}
assert!(cholesky(&[1.0, 2.0, 2.0, 1.0], 2).is_none());
}
}