use super::blas::{Op, Side, Uplo, gemm, trsm, with_scratch};
use super::{Matrix, Scalar};
use crate::math::complex::Float;
#[cfg(all(target_arch = "aarch64", target_endian = "little", not(miri)))]
mod aarch64;
#[derive(Clone, Debug)]
pub struct Cholesky<T> {
l: Matrix<T>,
}
impl<T: Scalar> Matrix<T> {
#[track_caller]
pub fn cholesky(&self) -> Option<Cholesky<T>> {
assert!(self.is_square(), "the matrix is not square");
let n = self.rows();
let mut l = self.clone();
match n {
0 => {}
1 => tiny::<T, 1>(&mut l.data)?,
2 => tiny::<T, 2>(&mut l.data)?,
3 => tiny::<T, 3>(&mut l.data)?,
4 => tiny::<T, 4>(&mut l.data)?,
5 if !T::IS_COMPLEX => tiny::<T, 5>(&mut l.data)?,
6 if !T::IS_COMPLEX => tiny::<T, 6>(&mut l.data)?,
7 if !T::IS_COMPLEX => tiny::<T, 7>(&mut l.data)?,
8 | 9 => dot(&mut l.data, n, n)?,
10 if size_of::<T>() == 8 => dot(&mut l.data, n, n)?,
_ if n <= narrow_order::<T>() => narrow(&mut l.data, n, n)?,
_ => {
let n1 = split::<T>(n);
with_scratch(n1 * n1, |work| factor(&mut l.data, n, n, work))?;
}
}
Some(Cholesky { l })
}
}
impl<T: Scalar> Cholesky<T> {
pub fn l(&self) -> Matrix<T> {
self.l.clone()
}
pub fn det(&self) -> T::Real {
let mut d = T::Real::ONE;
for x in self.l.diagonal() {
let r = x.re();
d *= r * r;
}
d
}
#[track_caller]
pub fn solve(&self, b: &Matrix<T>) -> Matrix<T> {
let n = self.l.rows();
assert_eq!(b.rows(), n, "the right-hand side has not as many rows");
let mut x = b.clone();
let cols = x.cols();
trsm(
Side::Left,
Uplo::Lower,
Op::N,
false,
n,
cols,
&self.l.data,
n,
&mut x.data,
n,
);
trsm(
Side::Left,
Uplo::Lower,
Op::C,
false,
n,
cols,
&self.l.data,
n,
&mut x.data,
n,
);
x
}
pub fn inverse(&self) -> Matrix<T> {
self.solve(&Matrix::identity(self.l.rows()))
}
}
const fn narrow_order<T: Scalar>() -> usize {
if T::IS_COMPLEX { 16 } else { 32 }
}
fn split<T: Scalar>(n: usize) -> usize {
(n / 2).next_multiple_of(narrow_order::<T>()).min(n - 1)
}
fn factor<T: Scalar>(a: &mut [T], n: usize, ld: usize, work: &mut [T]) -> Option<()> {
if n <= narrow_order::<T>() {
return narrow(a, n, ld);
}
let n1 = split::<T>(n);
let n2 = n - n1;
factor(a, n1, ld, work)?;
let (left, right) = a.split_at_mut(n1 * ld);
let l11 = &mut work[..n1 * n1];
for j in 0..n1 {
l11[j * n1 + j..(j + 1) * n1].copy_from_slice(&left[j * ld + j..j * ld + n1]);
}
trsm(
Side::Right,
Uplo::Lower,
Op::C,
false,
n2,
n1,
l11,
n1,
&mut left[n1..],
ld,
);
let l21 = &left[n1..];
let a22 = &mut right[n1..];
let mut j0 = 0;
while j0 < n2 {
let w = UPDATE.min(n2 - j0);
gemm(
n2 - j0,
w,
n1,
-T::one(),
&l21[j0..],
ld,
Op::N,
&l21[j0..],
ld,
Op::C,
T::one(),
&mut a22[j0 + j0 * ld..],
ld,
);
j0 += w;
}
factor(a22, n2, ld, work)?;
for j in 0..n2 {
right[j * ld..j * ld + n1].fill(T::zero());
}
Some(())
}
const UPDATE: usize = 64;
fn narrow<T: Scalar>(a: &mut [T], n: usize, ld: usize) -> Option<()> {
#[cfg(all(target_arch = "aarch64", target_endian = "little", not(miri)))]
if let Some(done) = narrow_neon(a, n, ld) {
return done.then_some(());
}
dot(a, n, ld)
}
fn dot<T: Scalar>(a: &mut [T], n: usize, ld: usize) -> Option<()> {
for j in 0..n {
let mut d = a[j + j * ld].re();
for p in 0..j {
d -= a[j + p * ld].abs2();
}
if d.is_nan() || d <= T::Real::ZERO {
return None;
}
let d = d.sqrt();
a[j + j * ld] = T::from_real(d);
let r = T::Real::ONE / d;
for i in j + 1..n {
let mut s = a[i + j * ld];
for p in 0..j {
s -= a[i + p * ld] * a[j + p * ld].conj();
}
a[i + j * ld] = s.scale(r);
}
}
for j in 1..n {
a[j * ld..j * ld + j].fill(T::zero());
}
Some(())
}
#[cfg(all(target_arch = "aarch64", target_endian = "little", not(miri)))]
fn narrow_neon<T: Scalar>(a: &mut [T], n: usize, ld: usize) -> Option<bool> {
use core::any::TypeId;
let is_f64 = TypeId::of::<T>() == TypeId::of::<f64>();
if !(is_f64 || TypeId::of::<T>() == TypeId::of::<f32>()) {
return None;
}
assert!((1..=aarch64::ROWS).contains(&n) && (n - 1) * ld + n <= a.len());
let len = a.len();
let a = a.as_mut_ptr();
Some(unsafe {
if is_f64 {
let a = a.cast::<f64>();
match n {
1..=2 => aarch64::leaf_f64::<1, 2>(a, n, ld, len),
3..=4 => aarch64::leaf_f64::<2, 4>(a, n, ld, len),
5..=6 => aarch64::leaf_f64::<3, 6>(a, n, ld, len),
7..=8 => aarch64::leaf_f64::<4, 8>(a, n, ld, len),
9..=12 => aarch64::leaf_f64::<6, 12>(a, n, ld, len),
13..=16 => aarch64::leaf_f64::<8, 16>(a, n, ld, len),
17..=24 => aarch64::leaf_f64::<12, 24>(a, n, ld, len),
_ => aarch64::leaf_f64::<16, 32>(a, n, ld, len),
}
} else {
let a = a.cast::<f32>();
match n {
1..=4 => aarch64::leaf_f32::<1, 4>(a, n, ld, len),
5..=8 => aarch64::leaf_f32::<2, 8>(a, n, ld, len),
9..=12 => aarch64::leaf_f32::<3, 12>(a, n, ld, len),
13..=16 => aarch64::leaf_f32::<4, 16>(a, n, ld, len),
17..=24 => aarch64::leaf_f32::<6, 24>(a, n, ld, len),
_ => aarch64::leaf_f32::<8, 32>(a, n, ld, len),
}
}
})
}
#[inline(always)]
fn tiny<T: Scalar, const N: usize>(a: &mut [T]) -> Option<()> {
let a = &mut a[..N * N];
for j in 0..N {
for p in 0..j {
let s = a[j + p * N].conj();
for i in j..N {
a[i + j * N] = a[i + j * N].mul_sub(a[i + p * N], s);
}
}
let d = pivot(&mut a[j + j * N..(j + 1) * N])?;
let r = T::Real::ONE / d;
for i in j + 1..N {
a[i + j * N] = a[i + j * N].scale(r);
}
}
for j in 1..N {
for i in 0..j {
a[i + j * N] = T::zero();
}
}
Some(())
}
#[inline(always)]
fn pivot<T: Scalar>(col: &mut [T]) -> Option<T::Real> {
let d = col[0].re();
if d.is_nan() || d <= T::Real::ZERO {
return None;
}
let d = d.sqrt();
col[0] = T::from_real(d);
Some(d)
}