use crate::core::errors::RustyQLibError;
pub fn cholesky_factor(a: &[Vec<f64>]) -> Result<Vec<Vec<f64>>, RustyQLibError> {
let n = a.len();
for i in 0..n {
if a[i].len() != n {
return Err(RustyQLibError::NumericalError("matrix must be square".to_string()));
}
for j in 0..i {
if (a[i][j] - a[j][i]).abs() > 1e-10 * (1.0 + a[i][j].abs()) {
return Err(RustyQLibError::NumericalError("matrix must be symmetric".to_string()));
}
}
}
let mut l = vec![vec![0.0; n]; n];
for i in 0..n {
for j in 0..=i {
let s: f64 = (0..j).map(|k| l[i][k] * l[j][k]).sum();
if i == j {
let d = a[i][i] - s;
if d < -1e-10 * (1.0 + a[i][i].abs()) {
return Err(RustyQLibError::NumericalError("matrix is not positive semi-definite".to_string()));
}
l[i][j] = d.max(0.0).sqrt();
} else if l[j][j] > 1e-12 {
l[i][j] = (a[i][j] - s) / l[j][j];
} else {
l[i][j] = 0.0;
}
}
}
Ok(l)
}
pub fn cholesky_solve(l: &[Vec<f64>], b: &[f64]) -> Result<Vec<f64>, RustyQLibError> {
let n = b.len();
if l.len() != n {
return Err(RustyQLibError::NumericalError("dimension mismatch".to_string()));
}
if (0..n).any(|i| l[i][i].abs() < 1e-300) {
return Err(RustyQLibError::NumericalError("factor is singular".to_string()));
}
let mut y = vec![0.0; n];
for i in 0..n {
let s: f64 = (0..i).map(|k| l[i][k] * y[k]).sum();
y[i] = (b[i] - s) / l[i][i];
}
let mut x = vec![0.0; n];
for i in (0..n).rev() {
let s: f64 = (i + 1..n).map(|k| l[k][i] * x[k]).sum();
x[i] = (y[i] - s) / l[i][i];
}
Ok(x)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn factor_reconstructs_the_matrix() {
let a = vec![
vec![4.0, 2.0, 0.6],
vec![2.0, 5.0, 1.5],
vec![0.6, 1.5, 2.0],
];
let l = cholesky_factor(&a).unwrap();
for i in 0..3 {
for j in 0..3 {
let recon: f64 = (0..3).map(|k| l[i][k] * l[j][k]).sum();
assert!((recon - a[i][j]).abs() < 1e-12, "[{i}][{j}]");
}
for j in i + 1..3 {
assert_eq!(l[i][j], 0.0);
}
}
}
#[test]
fn solve_inverts_the_system() {
let a = vec![vec![4.0, 1.0], vec![1.0, 3.0]];
let l = cholesky_factor(&a).unwrap();
let x = cholesky_solve(&l, &[1.0, 2.0]).unwrap();
assert!((x[0] - 1.0 / 11.0).abs() < 1e-13);
assert!((x[1] - 7.0 / 11.0).abs() < 1e-13);
}
#[test]
fn rejects_indefinite_and_tolerates_semi_definite() {
assert!(cholesky_factor(&[vec![1.0, 2.0], vec![2.0, 1.0]]).is_err());
let semi = vec![vec![1.0, 1.0], vec![1.0, 1.0]];
let l = cholesky_factor(&semi).unwrap();
assert!(cholesky_solve(&l, &[1.0, 1.0]).is_err());
assert!(cholesky_factor(&[vec![1.0, 0.5], vec![0.4, 1.0]]).is_err());
}
}