pub fn gauss(mut p: Vec<Vec<f64>>) -> Vec<f64> {
let n = p.len();
for col in 0..n {
let pivot = (col..n).max_by(|&a, &b| p[a][col].abs().total_cmp(&p[b][col].abs())).unwrap();
p.swap(col, pivot);
let pivot_row = p[col].clone();
for row in &mut p[col + 1..] {
let f = row[col] / pivot_row[col];
for (x, y) in row[col..].iter_mut().zip(&pivot_row[col..]) {
*x -= f * y;
}
}
}
let mut q = vec![0.0; n];
for row in (0..n).rev() {
let s: f64 = (row + 1..n).map(|k| p[row][k] * q[k]).sum();
q[row] = (p[row][n] - s) / p[row][row];
}
q
}
pub fn cholesky(a: &mut [f64], n: usize) {
for j in 0..n {
let mut d = a[j * n + j];
for p in 0..j {
d -= a[j * n + p] * a[j * n + p];
}
assert!(d > 0.0, "normal matrix not positive definite at {j}");
let d = d.sqrt();
a[j * n + j] = d;
for i in j + 1..n {
let mut s = a[i * n + j];
for p in 0..j {
s -= a[i * n + p] * a[j * n + p];
}
a[i * n + j] = s / d;
}
}
}
pub fn cholesky_solve(l: &[f64], n: usize, b: &[f64]) -> Vec<f64> {
let mut z = b.to_vec();
for i in 0..n {
for p in 0..i {
z[i] -= l[i * n + p] * z[p];
}
z[i] /= l[i * n + i];
}
for i in (0..n).rev() {
for p in i + 1..n {
z[i] -= l[p * n + i] * z[p];
}
z[i] /= l[i * n + i];
}
z
}
pub fn cholesky_inverse_diagonal(l: &[f64], n: usize) -> Vec<f64> {
let mut diag = vec![0.0; n];
let mut col = vec![0.0; n];
for j in 0..n {
col.iter_mut().for_each(|v| *v = 0.0);
col[j] = 1.0 / l[j * n + j];
for i in j + 1..n {
let mut s = 0.0;
for p in j..i {
s -= l[i * n + p] * col[p];
}
col[i] = s / l[i * n + i];
}
diag[j] = col[j..].iter().map(|c| c * c).sum();
}
diag
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gauss_needs_its_pivot() {
let x = gauss(vec![vec![0.0, 1.0, 2.0], vec![1.0, 0.0, 3.0]]);
assert_eq!(x, vec![3.0, 2.0]);
let n = 6;
let a: Vec<Vec<f64>> = (0..n).map(|i| (0..n).map(|j| 1.0 / (1.0 + i as f64 + 2.0 * j as f64) + if i == j { 1.0 } else { 0.0 }).collect()).collect();
let truth: Vec<f64> = (0..n).map(|i| i as f64 - 2.5).collect();
let rows = a.iter().map(|r| {
let mut row = r.clone();
row.push(r.iter().zip(&truth).map(|(x, y)| x * y).sum());
row
});
let x = gauss(rows.collect());
assert!(x.iter().zip(&truth).all(|(a, b)| (a - b).abs() < 1e-12), "{x:?}");
}
#[test]
fn cholesky_inverse_diagonal_matches_the_inverse() {
let full = [[4.0, 12.0, -16.0], [12.0, 37.0, -43.0], [-16.0, -43.0, 98.0]];
let mut a = vec![4.0, 0.0, 0.0, 12.0, 37.0, 0.0, -16.0, -43.0, 98.0];
cholesky(&mut a, 3);
let diag = cholesky_inverse_diagonal(&a, 3);
for (d, exact) in diag.iter().zip([1777.0 / 36.0, 136.0 / 36.0, 4.0 / 36.0]) {
assert!((d / exact - 1.0).abs() < 1e-12, "{d} vs {exact}");
}
for (j, d) in diag.iter().enumerate() {
let mut e = [0.0; 3];
e[j] = 1.0;
let col = cholesky_solve(&a, 3, &e);
assert!((col[j] - d).abs() < 1e-10 * d.abs(), "{j}: {} vs {d}", col[j]);
for (i, row) in full.iter().enumerate() {
let v: f64 = row.iter().zip(&col).map(|(x, y)| x * y).sum();
assert!((v - e[i]).abs() < 1e-10);
}
}
}
}