use crate::{linalg::LinalgError, FloatDType, NdArray, Result};
pub struct CholeskyResult<T: FloatDType> {
pub l: NdArray<T>,
}
impl<T: FloatDType> CholeskyResult<T> {
pub fn reconstruct(&self) -> Result<NdArray<T>> {
self.l.matmul(&self.l.transpose_last()?)
}
}
pub fn cholesky<T: FloatDType>(arr: &NdArray<T>) -> Result<CholeskyResult<T>> {
let mat = arr.matrix_view_unsafe()?;
let (n, _) = mat.shape();
let l_arr = NdArray::<T>::zeros(mat.shape())?;
unsafe {
let mut l = l_arr.matrix_view_unsafe().unwrap();
for i in 0..n {
let mut sum = T::zero();
for k in 0..i {
sum = sum + l.g(i, k) * l.g(i, k);
}
let diag = mat.g(i, i) - sum;
if diag <= T::zero() {
return Err(LinalgError::ExpectPositiveDefiniteMatrix { op: "cholesky" })?;
}
l.s(i, i, diag.sqrt());
for j in i+1..n {
let mut sum = T::zero();
for k in 0..i {
sum = sum + l.g(j, k) * l.g(i, k);
}
l.s(j, i, (mat.g(j, i) - sum) / l.g(i, i));
}
}
}
Ok(CholeskyResult {l: l_arr})
}
#[cfg(test)]
mod test {
use crate::NdArray;
use crate::linalg;
#[test]
fn test_cholesky_simple() {
let a = NdArray::new(&[
[4., 12., -16.],
[12., 37., -43.],
[-16., -43., 98.],
]).unwrap();
let result = linalg::cholesky(&a).unwrap();
let a_rec = result.reconstruct().unwrap();
assert!(a_rec.allclose(&a, 1e-6, 1e-6));
}
#[test]
fn test_cholesky_identity() {
let a = NdArray::<f64>::eye(4).unwrap();
let result = linalg::cholesky(&a).unwrap();
let rec = result.reconstruct().unwrap();
assert!(rec.allclose(&a, 1e-6, 1e-6));
}
#[test]
fn test_cholesky_random_pos_def() {
let b = NdArray::new(&[
[1., 2., 3.],
[4., 5., 6.],
[7., 8., 10.],
]).unwrap();
let a = b.matmul(&b.transpose_last().unwrap()).unwrap();
let result = linalg::cholesky(&a).unwrap();
let a_rec = result.reconstruct().unwrap();
assert!(a_rec.allclose(&a, 1e-6, 1e-6));
}
#[test]
#[should_panic]
fn test_cholesky_non_pos_def() {
let a = NdArray::new(&[
[1., 2.],
[2., 1.],
]).unwrap();
linalg::cholesky(&a).unwrap();
}
#[test]
fn test_cholesky_high_dim() {
let a = NdArray::<f64>::randn(0., 1., (10, 10)).unwrap();
let h = a.matmul(&a.transpose_last().unwrap()).unwrap();
let result = linalg::cholesky(&h).unwrap();
let h_rec = result.reconstruct().unwrap();
assert!(h_rec.allclose(&h, 1e-6, 1e-6));
}
}