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
}
pub fn symmetric_eigen(a: &[f64], n: usize) -> (Vec<f64>, Vec<f64>) {
if n > 32 { householder_ql(a, n) } else { jacobi(a, n) }
}
pub fn householder_ql(a: &[f64], n: usize) -> (Vec<f64>, Vec<f64>) {
let mut a = a.to_vec();
let mut q: Vec<f64> = (0..n * n).map(|i| if i / n == i % n { 1.0 } else { 0.0 }).collect();
for k in 0..n.saturating_sub(2) {
let norm = (k + 1..n).map(|i| a[i * n + k].powi(2)).sum::<f64>().sqrt();
if norm == 0.0 {
continue;
}
let mut v = vec![0.0; n];
let x0 = a[(k + 1) * n + k];
let alpha = if x0 >= 0.0 { -norm } else { norm };
for i in k + 1..n {
v[i] = a[i * n + k];
}
v[k + 1] -= alpha;
let vn = v.iter().map(|x| x * x).sum::<f64>().sqrt();
v.iter_mut().for_each(|x| *x /= vn);
let p: Vec<f64> = (0..n).map(|i| (k + 1..n).map(|j| a[i * n + j] * v[j]).sum()).collect();
let vp: f64 = (k + 1..n).map(|i| v[i] * p[i]).sum();
let qv: Vec<f64> = (0..n).map(|i| p[i] - vp * v[i]).collect();
for i in 0..n {
for j in 0..n {
a[i * n + j] -= 2.0 * (v[i] * qv[j] + qv[i] * v[j]);
}
}
for i in 0..n {
let dot: f64 = (k + 1..n).map(|j| q[i * n + j] * v[j]).sum();
for j in k + 1..n {
q[i * n + j] -= 2.0 * dot * v[j];
}
}
}
let mut d: Vec<f64> = (0..n).map(|i| a[i * n + i]).collect();
let mut e: Vec<f64> = (0..n).map(|i| if i + 1 < n { a[(i + 1) * n + i] } else { 0.0 }).collect();
for l in 0..n {
for _ in 0..100 {
let mut m = l;
while m + 1 < n && e[m].abs() > f64::EPSILON * (d[m].abs() + d[m + 1].abs()) {
m += 1;
}
if m == l {
break;
}
let mut g = (d[l + 1] - d[l]) / (2.0 * e[l]);
let r = g.hypot(1.0);
g = d[m] - d[l] + e[l] / (g + if g >= 0.0 { r } else { -r });
let (mut s, mut c, mut p) = (1.0, 1.0, 0.0);
let mut i = m;
let mut underflow = false;
while i > l {
i -= 1;
let (f, b) = (s * e[i], c * e[i]);
let r = f.hypot(g);
e[i + 1] = r;
if r == 0.0 {
d[i + 1] -= p;
e[m] = 0.0;
underflow = true;
break;
}
(s, c) = (f / r, g / r);
g = d[i + 1] - p;
let r = (d[i] - g) * s + 2.0 * c * b;
p = s * r;
d[i + 1] = g + p;
g = c * r - b;
for row in q.chunks_mut(n) {
let (x, y) = (row[i], row[i + 1]);
(row[i], row[i + 1]) = (c * x - s * y, s * x + c * y);
}
}
if underflow {
continue;
}
d[l] -= p;
e[l] = g;
e[m] = 0.0;
}
}
(d, q)
}
fn jacobi(a: &[f64], n: usize) -> (Vec<f64>, Vec<f64>) {
let mut a = a.to_vec();
let mut v: Vec<f64> = (0..n * n).map(|i| if i / n == i % n { 1.0 } else { 0.0 }).collect();
let scale = a.iter().map(|x| x * x).sum::<f64>();
for _ in 0..100 {
let off: f64 = (0..n).flat_map(|p| (p + 1..n).map(move |q| (p, q))).map(|(p, q)| a[p * n + q].powi(2)).sum();
if off <= 1e-34 * scale {
break;
}
for p in 0..n {
for q in p + 1..n {
if a[p * n + q] == 0.0 {
continue;
}
let theta = (a[q * n + q] - a[p * n + p]) / (2.0 * a[p * n + q]);
let t = theta.signum() / (theta.abs() + theta.hypot(1.0));
let c = 1.0 / t.hypot(1.0);
let s = t * c;
for k in 0..n {
let (kp, kq) = (a[k * n + p], a[k * n + q]);
(a[k * n + p], a[k * n + q]) = (c * kp - s * kq, s * kp + c * kq);
}
for k in 0..n {
let (pk, qk) = (a[p * n + k], a[q * n + k]);
(a[p * n + k], a[q * n + k]) = (c * pk - s * qk, s * pk + c * qk);
let (kp, kq) = (v[k * n + p], v[k * n + q]);
(v[k * n + p], v[k * n + q]) = (c * kp - s * kq, s * kp + c * kq);
}
}
}
}
((0..n).map(|k| a[k * n + k]).collect(), v)
}
#[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);
}
}
}
#[test]
fn householder_ql_agrees_with_jacobi() {
let n = 60;
let mut state = 3u64;
let mut next = || {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
(state >> 11) as f64 / (1u64 << 53) as f64 - 0.5
};
let b: Vec<f64> = (0..n * n).map(|_| next()).collect();
let mut qm = b.clone();
for j in 0..n {
for k in 0..j {
let dot: f64 = (0..n).map(|i| qm[i * n + j] * qm[i * n + k]).sum();
(0..n).for_each(|i| qm[i * n + j] -= dot * qm[i * n + k]);
}
let norm = (0..n).map(|i| qm[i * n + j].powi(2)).sum::<f64>().sqrt();
(0..n).for_each(|i| qm[i * n + j] /= norm);
}
let lambda: Vec<f64> = (0..n).map(|k| 10f64.powf(-6.0 * k as f64 / (n - 1) as f64)).collect();
let a: Vec<f64> = (0..n * n).map(|ij| (0..n).map(|k| qm[(ij / n) * n + k] * lambda[k] * qm[(ij % n) * n + k]).sum()).collect();
for (values, vectors) in [householder_ql(&a, n), jacobi(&a, n)] {
for k in 0..n {
for i in 0..n {
let av: f64 = (0..n).map(|j| a[i * n + j] * vectors[j * n + k]).sum();
assert!((av - values[k] * vectors[i * n + k]).abs() < 1e-14, "A v = λ v");
}
let dot: f64 = (0..n).map(|i| vectors[i * n + k] * vectors[i * n + (k + 1) % n]).sum();
assert!(dot.abs() < 1e-13 && ((0..n).map(|i| vectors[i * n + k].powi(2)).sum::<f64>() - 1.0).abs() < 1e-13);
}
let mut got = values.clone();
got.sort_by(|a, b| b.total_cmp(a));
for (g, l) in got.iter().zip(&lambda) {
assert!((g - l).abs() < 1e-13 * l.max(1e-2), "{g} vs {l}");
}
}
}
#[test]
fn symmetric_eigen_of_the_discrete_laplacian() {
let n: usize = 12;
let a: Vec<f64> = (0..n * n).map(|i| match (i / n).abs_diff(i % n) { 0 => 2.0, 1 => -1.0, _ => 0.0 }).collect();
let (mut values, vectors) = symmetric_eigen(&a, n);
for k in 0..n {
for i in 0..n {
let av: f64 = (0..n).map(|j| a[i * n + j] * vectors[j * n + k]).sum();
assert!((av - values[k] * vectors[i * n + k]).abs() < 1e-14);
}
for l in 0..n {
let dot: f64 = (0..n).map(|i| vectors[i * n + k] * vectors[i * n + l]).sum();
assert!((dot - if k == l { 1.0 } else { 0.0 }).abs() < 1e-14);
}
}
values.sort_by(f64::total_cmp);
for (k, v) in values.iter().enumerate() {
let want = 2.0 - 2.0 * ((k + 1) as f64 * core::f64::consts::PI / (n + 1) as f64).cos();
assert!((v - want).abs() < 1e-14, "λ{k} = {v} vs {want}");
}
}
}