use num_traits::FromPrimitive;
use oxiblas_core::scalar::{Field, Real, Scalar};
use oxiblas_matrix::{Mat, MatRef};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Uplo {
Lower,
Upper,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PackedCholeskyError {
NotPositiveDefinite {
index: usize,
},
InvalidSize {
expected: usize,
actual: usize,
},
DimensionMismatch {
expected: usize,
actual: usize,
},
}
impl core::fmt::Display for PackedCholeskyError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
PackedCholeskyError::NotPositiveDefinite { index } => {
write!(
f,
"Matrix is not positive definite (detected at index {index})"
)
}
PackedCholeskyError::InvalidSize { expected, actual } => {
write!(f, "Invalid packed size: expected {expected}, got {actual}")
}
PackedCholeskyError::DimensionMismatch { expected, actual } => {
write!(f, "Dimension mismatch: expected {expected}, got {actual}")
}
}
}
}
impl std::error::Error for PackedCholeskyError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PackedLdltError {
Singular {
index: usize,
},
InvalidSize {
expected: usize,
actual: usize,
},
DimensionMismatch {
expected: usize,
actual: usize,
},
}
impl core::fmt::Display for PackedLdltError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
PackedLdltError::Singular { index } => {
write!(f, "Matrix is singular (zero pivot at index {index})")
}
PackedLdltError::InvalidSize { expected, actual } => {
write!(f, "Invalid packed size: expected {expected}, got {actual}")
}
PackedLdltError::DimensionMismatch { expected, actual } => {
write!(f, "Dimension mismatch: expected {expected}, got {actual}")
}
}
}
}
impl std::error::Error for PackedLdltError {}
#[inline]
pub fn packed_lower_index(n: usize, i: usize, j: usize) -> usize {
debug_assert!(i >= j);
debug_assert!(i < n);
j * n - (j * (j + 1)) / 2 + i
}
#[inline]
pub fn packed_upper_index(_n: usize, i: usize, j: usize) -> usize {
debug_assert!(i <= j);
(j * (j + 1)) / 2 + i
}
pub fn dense_to_packed_lower<T: Scalar>(a: MatRef<'_, T>) -> Vec<T> {
let n = a.nrows();
debug_assert_eq!(n, a.ncols());
let packed_size = n * (n + 1) / 2;
let mut ap = vec![T::zero(); packed_size];
for j in 0..n {
for i in j..n {
ap[packed_lower_index(n, i, j)] = a[(i, j)];
}
}
ap
}
pub fn dense_to_packed_upper<T: Scalar>(a: MatRef<'_, T>) -> Vec<T> {
let n = a.nrows();
debug_assert_eq!(n, a.ncols());
let packed_size = n * (n + 1) / 2;
let mut ap = vec![T::zero(); packed_size];
for j in 0..n {
for i in 0..=j {
ap[packed_upper_index(n, i, j)] = a[(i, j)];
}
}
ap
}
pub fn packed_lower_to_dense<T: Scalar + bytemuck::Zeroable>(ap: &[T], n: usize) -> Mat<T> {
let mut a = Mat::zeros(n, n);
for j in 0..n {
for i in j..n {
let val = ap[packed_lower_index(n, i, j)];
a[(i, j)] = val;
a[(j, i)] = val; }
}
a
}
pub fn packed_upper_to_dense<T: Scalar + bytemuck::Zeroable>(ap: &[T], n: usize) -> Mat<T> {
let mut a = Mat::zeros(n, n);
for j in 0..n {
for i in 0..=j {
let val = ap[packed_upper_index(n, i, j)];
a[(i, j)] = val;
a[(j, i)] = val; }
}
a
}
#[derive(Clone, Debug)]
pub struct PackedCholesky<T: Scalar> {
factor: Vec<T>,
n: usize,
uplo: Uplo,
}
impl<T: Field + Real + bytemuck::Zeroable> PackedCholesky<T> {
pub fn compute(ap: &[T], n: usize, uplo: Uplo) -> Result<Self, PackedCholeskyError> {
let expected_size = n * (n + 1) / 2;
if ap.len() != expected_size {
return Err(PackedCholeskyError::InvalidSize {
expected: expected_size,
actual: ap.len(),
});
}
if n == 0 {
return Ok(PackedCholesky {
factor: Vec::new(),
n: 0,
uplo,
});
}
let mut factor = ap.to_vec();
match uplo {
Uplo::Lower => {
for j in 0..n {
let diag_idx = packed_lower_index(n, j, j);
let mut ajj = factor[diag_idx];
for k in 0..j {
let ljk = factor[packed_lower_index(n, j, k)];
ajj = ajj - ljk * ljk;
}
let tol = <T as Scalar>::epsilon()
* <T as FromPrimitive>::from_usize(n).unwrap_or(T::one());
if ajj <= tol {
return Err(PackedCholeskyError::NotPositiveDefinite { index: j });
}
factor[diag_idx] = Real::sqrt(ajj);
let ljj = factor[diag_idx];
for i in (j + 1)..n {
let mut aij = factor[packed_lower_index(n, i, j)];
for k in 0..j {
let lik = factor[packed_lower_index(n, i, k)];
let ljk = factor[packed_lower_index(n, j, k)];
aij = aij - lik * ljk;
}
factor[packed_lower_index(n, i, j)] = aij / ljj;
}
}
}
Uplo::Upper => {
for j in 0..n {
let diag_idx = packed_upper_index(n, j, j);
let mut ajj = factor[diag_idx];
for k in 0..j {
let ukj = factor[packed_upper_index(n, k, j)];
ajj = ajj - ukj * ukj;
}
let tol = <T as Scalar>::epsilon()
* <T as FromPrimitive>::from_usize(n).unwrap_or(T::one());
if ajj <= tol {
return Err(PackedCholeskyError::NotPositiveDefinite { index: j });
}
factor[diag_idx] = Real::sqrt(ajj);
let ujj = factor[diag_idx];
for i in (j + 1)..n {
let mut aji = factor[packed_upper_index(n, j, i)];
for k in 0..j {
let ukj = factor[packed_upper_index(n, k, j)];
let uki = factor[packed_upper_index(n, k, i)];
aji = aji - ukj * uki;
}
factor[packed_upper_index(n, j, i)] = aji / ujj;
}
}
}
}
Ok(PackedCholesky { factor, n, uplo })
}
#[inline]
pub fn size(&self) -> usize {
self.n
}
pub fn factor(&self) -> &[T] {
&self.factor
}
pub fn uplo(&self) -> Uplo {
self.uplo
}
pub fn solve(&self, b: MatRef<'_, T>) -> Result<Mat<T>, PackedCholeskyError> {
if b.nrows() != self.n {
return Err(PackedCholeskyError::DimensionMismatch {
expected: self.n,
actual: b.nrows(),
});
}
let m = b.ncols();
let mut x = Mat::zeros(self.n, m);
for j in 0..m {
for i in 0..self.n {
x[(i, j)] = b[(i, j)];
}
}
match self.uplo {
Uplo::Lower => {
for k in 0..self.n {
let lkk = self.factor[packed_lower_index(self.n, k, k)];
for j in 0..m {
x[(k, j)] = x[(k, j)] / lkk;
}
for i in (k + 1)..self.n {
let lik = self.factor[packed_lower_index(self.n, i, k)];
for j in 0..m {
x[(i, j)] = x[(i, j)] - lik * x[(k, j)];
}
}
}
for k in (0..self.n).rev() {
let lkk = self.factor[packed_lower_index(self.n, k, k)];
for j in 0..m {
x[(k, j)] = x[(k, j)] / lkk;
}
for i in 0..k {
let lki = self.factor[packed_lower_index(self.n, k, i)];
for j in 0..m {
x[(i, j)] = x[(i, j)] - lki * x[(k, j)];
}
}
}
}
Uplo::Upper => {
for k in 0..self.n {
let ukk = self.factor[packed_upper_index(self.n, k, k)];
for j in 0..m {
x[(k, j)] = x[(k, j)] / ukk;
}
for i in (k + 1)..self.n {
let uki = self.factor[packed_upper_index(self.n, k, i)];
for j in 0..m {
x[(i, j)] = x[(i, j)] - uki * x[(k, j)];
}
}
}
for k in (0..self.n).rev() {
let ukk = self.factor[packed_upper_index(self.n, k, k)];
for j in 0..m {
x[(k, j)] = x[(k, j)] / ukk;
}
for i in 0..k {
let uik = self.factor[packed_upper_index(self.n, i, k)];
for j in 0..m {
x[(i, j)] = x[(i, j)] - uik * x[(k, j)];
}
}
}
}
}
Ok(x)
}
pub fn determinant(&self) -> T {
if self.n == 0 {
return T::one();
}
let mut det = T::one();
for i in 0..self.n {
let diag = match self.uplo {
Uplo::Lower => self.factor[packed_lower_index(self.n, i, i)],
Uplo::Upper => self.factor[packed_upper_index(self.n, i, i)],
};
det = det * diag * diag;
}
det
}
}
#[derive(Clone, Debug)]
pub struct PackedLdlt<T: Scalar> {
factor: Vec<T>,
diagonal: Vec<T>,
n: usize,
uplo: Uplo,
}
impl<T: Field + Real + bytemuck::Zeroable> PackedLdlt<T> {
pub fn compute(ap: &[T], n: usize, uplo: Uplo) -> Result<Self, PackedLdltError> {
let expected_size = n * (n + 1) / 2;
if ap.len() != expected_size {
return Err(PackedLdltError::InvalidSize {
expected: expected_size,
actual: ap.len(),
});
}
if n == 0 {
return Ok(PackedLdlt {
factor: Vec::new(),
diagonal: Vec::new(),
n: 0,
uplo,
});
}
let mut factor = ap.to_vec();
let mut diagonal = vec![T::zero(); n];
match uplo {
Uplo::Lower => {
for j in 0..n {
let diag_idx = packed_lower_index(n, j, j);
let mut dj = factor[diag_idx];
for k in 0..j {
let ljk = factor[packed_lower_index(n, j, k)];
dj = dj - ljk * ljk * diagonal[k];
}
let tol = <T as Scalar>::epsilon()
* <T as FromPrimitive>::from_usize(n).unwrap_or(T::one());
if Scalar::abs(dj) <= tol {
return Err(PackedLdltError::Singular { index: j });
}
diagonal[j] = dj;
factor[diag_idx] = T::one();
for i in (j + 1)..n {
let idx = packed_lower_index(n, i, j);
let mut lij = factor[idx];
for k in 0..j {
let lik = factor[packed_lower_index(n, i, k)];
let ljk = factor[packed_lower_index(n, j, k)];
lij = lij - lik * ljk * diagonal[k];
}
factor[idx] = lij / dj;
}
}
}
Uplo::Upper => {
for j in 0..n {
let diag_idx = packed_upper_index(n, j, j);
let mut dj = factor[diag_idx];
for k in 0..j {
let ukj = factor[packed_upper_index(n, k, j)];
dj = dj - ukj * ukj * diagonal[k];
}
let tol = <T as Scalar>::epsilon()
* <T as FromPrimitive>::from_usize(n).unwrap_or(T::one());
if Scalar::abs(dj) <= tol {
return Err(PackedLdltError::Singular { index: j });
}
diagonal[j] = dj;
factor[diag_idx] = T::one();
for i in (j + 1)..n {
let idx = packed_upper_index(n, j, i);
let mut uji = factor[idx];
for k in 0..j {
let ukj = factor[packed_upper_index(n, k, j)];
let uki = factor[packed_upper_index(n, k, i)];
uji = uji - ukj * uki * diagonal[k];
}
factor[idx] = uji / dj;
}
}
}
}
Ok(PackedLdlt {
factor,
diagonal,
n,
uplo,
})
}
#[inline]
pub fn size(&self) -> usize {
self.n
}
pub fn factor(&self) -> &[T] {
&self.factor
}
pub fn diagonal(&self) -> &[T] {
&self.diagonal
}
pub fn uplo(&self) -> Uplo {
self.uplo
}
pub fn is_positive_definite(&self) -> bool {
self.diagonal.iter().all(|&d| d > T::zero())
}
pub fn solve(&self, b: MatRef<'_, T>) -> Result<Mat<T>, PackedLdltError> {
if b.nrows() != self.n {
return Err(PackedLdltError::DimensionMismatch {
expected: self.n,
actual: b.nrows(),
});
}
let m = b.ncols();
let mut x = Mat::zeros(self.n, m);
for j in 0..m {
for i in 0..self.n {
x[(i, j)] = b[(i, j)];
}
}
match self.uplo {
Uplo::Lower => {
for k in 0..self.n {
for i in (k + 1)..self.n {
let lik = self.factor[packed_lower_index(self.n, i, k)];
for j in 0..m {
x[(i, j)] = x[(i, j)] - lik * x[(k, j)];
}
}
}
for k in 0..self.n {
let dk = self.diagonal[k];
for j in 0..m {
x[(k, j)] = x[(k, j)] / dk;
}
}
for k in (0..self.n).rev() {
for i in 0..k {
let lki = self.factor[packed_lower_index(self.n, k, i)];
for j in 0..m {
x[(i, j)] = x[(i, j)] - lki * x[(k, j)];
}
}
}
}
Uplo::Upper => {
for k in 0..self.n {
for i in (k + 1)..self.n {
let uki = self.factor[packed_upper_index(self.n, k, i)];
for j in 0..m {
x[(i, j)] = x[(i, j)] - uki * x[(k, j)];
}
}
}
for k in 0..self.n {
let dk = self.diagonal[k];
for j in 0..m {
x[(k, j)] = x[(k, j)] / dk;
}
}
for k in (0..self.n).rev() {
for i in 0..k {
let uik = self.factor[packed_upper_index(self.n, i, k)];
for j in 0..m {
x[(i, j)] = x[(i, j)] - uik * x[(k, j)];
}
}
}
}
}
Ok(x)
}
pub fn determinant(&self) -> T {
if self.n == 0 {
return T::one();
}
let mut det = T::one();
for &d in &self.diagonal {
det = det * d;
}
det
}
}
pub fn ppsv<T: Field + Real + bytemuck::Zeroable>(
ap: &[T],
n: usize,
uplo: Uplo,
b: MatRef<'_, T>,
) -> Result<Mat<T>, PackedCholeskyError> {
let chol = PackedCholesky::compute(ap, n, uplo)?;
chol.solve(b)
}
pub fn spsv<T: Field + Real + bytemuck::Zeroable>(
ap: &[T],
n: usize,
uplo: Uplo,
b: MatRef<'_, T>,
) -> Result<Mat<T>, PackedLdltError> {
let ldlt = PackedLdlt::compute(ap, n, uplo)?;
ldlt.solve(b)
}
#[cfg(test)]
mod tests {
use super::*;
use oxiblas_matrix::Mat;
#[test]
fn test_packed_indexing_lower() {
assert_eq!(packed_lower_index(3, 0, 0), 0);
assert_eq!(packed_lower_index(3, 1, 0), 1);
assert_eq!(packed_lower_index(3, 2, 0), 2);
assert_eq!(packed_lower_index(3, 1, 1), 3);
assert_eq!(packed_lower_index(3, 2, 1), 4);
assert_eq!(packed_lower_index(3, 2, 2), 5);
}
#[test]
fn test_packed_indexing_upper() {
assert_eq!(packed_upper_index(3, 0, 0), 0);
assert_eq!(packed_upper_index(3, 0, 1), 1);
assert_eq!(packed_upper_index(3, 1, 1), 2);
assert_eq!(packed_upper_index(3, 0, 2), 3);
assert_eq!(packed_upper_index(3, 1, 2), 4);
assert_eq!(packed_upper_index(3, 2, 2), 5);
}
#[test]
fn test_dense_to_packed_lower() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0, 1.0], &[2.0, 5.0, 3.0], &[1.0, 3.0, 6.0]]);
let ap = dense_to_packed_lower(a.as_ref());
assert_eq!(ap.len(), 6);
assert_eq!(ap[0], 4.0); assert_eq!(ap[1], 2.0); assert_eq!(ap[2], 1.0); assert_eq!(ap[3], 5.0); assert_eq!(ap[4], 3.0); assert_eq!(ap[5], 6.0); }
#[test]
fn test_dense_to_packed_upper() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0, 1.0], &[2.0, 5.0, 3.0], &[1.0, 3.0, 6.0]]);
let ap = dense_to_packed_upper(a.as_ref());
assert_eq!(ap.len(), 6);
assert_eq!(ap[0], 4.0); assert_eq!(ap[1], 2.0); assert_eq!(ap[2], 5.0); assert_eq!(ap[3], 1.0); assert_eq!(ap[4], 3.0); assert_eq!(ap[5], 6.0); }
#[test]
fn test_packed_lower_roundtrip() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0, 1.0], &[2.0, 5.0, 3.0], &[1.0, 3.0, 6.0]]);
let ap = dense_to_packed_lower(a.as_ref());
let a_recovered = packed_lower_to_dense(&ap, 3);
for i in 0..3 {
for j in 0..3 {
assert!(
(a[(i, j)] - a_recovered[(i, j)]).abs() < 1e-14,
"Mismatch at ({}, {})",
i,
j
);
}
}
}
#[test]
fn test_packed_upper_roundtrip() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0, 1.0], &[2.0, 5.0, 3.0], &[1.0, 3.0, 6.0]]);
let ap = dense_to_packed_upper(a.as_ref());
let a_recovered = packed_upper_to_dense(&ap, 3);
for i in 0..3 {
for j in 0..3 {
assert!(
(a[(i, j)] - a_recovered[(i, j)]).abs() < 1e-14,
"Mismatch at ({}, {})",
i,
j
);
}
}
}
#[test]
fn test_packed_cholesky_lower() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0], &[2.0, 5.0]]);
let ap = dense_to_packed_lower(a.as_ref());
let chol = PackedCholesky::compute(&ap, 2, Uplo::Lower).unwrap();
let det = chol.determinant();
assert!((det - 16.0).abs() < 1e-10, "det = {}", det);
}
#[test]
fn test_packed_cholesky_upper() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0], &[2.0, 5.0]]);
let ap = dense_to_packed_upper(a.as_ref());
let chol = PackedCholesky::compute(&ap, 2, Uplo::Upper).unwrap();
let det = chol.determinant();
assert!((det - 16.0).abs() < 1e-10, "det = {}", det);
}
#[test]
fn test_packed_cholesky_solve_lower() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0], &[2.0, 5.0]]);
let b: Mat<f64> = Mat::from_rows(&[&[8.0], &[11.0]]);
let ap = dense_to_packed_lower(a.as_ref());
let chol = PackedCholesky::compute(&ap, 2, Uplo::Lower).unwrap();
let x = chol.solve(b.as_ref()).unwrap();
let ax0 = 4.0 * x[(0, 0)] + 2.0 * x[(1, 0)];
let ax1 = 2.0 * x[(0, 0)] + 5.0 * x[(1, 0)];
assert!((ax0 - 8.0).abs() < 1e-10, "Ax[0] = {}", ax0);
assert!((ax1 - 11.0).abs() < 1e-10, "Ax[1] = {}", ax1);
}
#[test]
fn test_packed_cholesky_solve_upper() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0], &[2.0, 5.0]]);
let b: Mat<f64> = Mat::from_rows(&[&[8.0], &[11.0]]);
let ap = dense_to_packed_upper(a.as_ref());
let chol = PackedCholesky::compute(&ap, 2, Uplo::Upper).unwrap();
let x = chol.solve(b.as_ref()).unwrap();
let ax0 = 4.0 * x[(0, 0)] + 2.0 * x[(1, 0)];
let ax1 = 2.0 * x[(0, 0)] + 5.0 * x[(1, 0)];
assert!((ax0 - 8.0).abs() < 1e-10, "Ax[0] = {}", ax0);
assert!((ax1 - 11.0).abs() < 1e-10, "Ax[1] = {}", ax1);
}
#[test]
fn test_packed_cholesky_3x3() {
let a: Mat<f64> = Mat::from_rows(&[
&[4.0, 12.0, -16.0],
&[12.0, 37.0, -43.0],
&[-16.0, -43.0, 98.0],
]);
let ap = dense_to_packed_lower(a.as_ref());
let chol = PackedCholesky::compute(&ap, 3, Uplo::Lower).unwrap();
let b: Mat<f64> = Mat::from_rows(&[&[1.0], &[2.0], &[3.0]]);
let x = chol.solve(b.as_ref()).unwrap();
let ax0 = 4.0 * x[(0, 0)] + 12.0 * x[(1, 0)] - 16.0 * x[(2, 0)];
let ax1 = 12.0 * x[(0, 0)] + 37.0 * x[(1, 0)] - 43.0 * x[(2, 0)];
let ax2 = -16.0 * x[(0, 0)] - 43.0 * x[(1, 0)] + 98.0 * x[(2, 0)];
assert!((ax0 - 1.0).abs() < 1e-10, "Ax[0] = {}", ax0);
assert!((ax1 - 2.0).abs() < 1e-10, "Ax[1] = {}", ax1);
assert!((ax2 - 3.0).abs() < 1e-10, "Ax[2] = {}", ax2);
}
#[test]
fn test_packed_cholesky_not_spd() {
let a: Mat<f64> = Mat::from_rows(&[&[1.0, 2.0], &[2.0, 1.0]]);
let ap = dense_to_packed_lower(a.as_ref());
let result = PackedCholesky::compute(&ap, 2, Uplo::Lower);
assert!(result.is_err());
}
#[test]
fn test_packed_ldlt_lower() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0], &[2.0, 5.0]]);
let ap = dense_to_packed_lower(a.as_ref());
let ldlt = PackedLdlt::compute(&ap, 2, Uplo::Lower).unwrap();
assert!(ldlt.is_positive_definite());
let det = ldlt.determinant();
assert!((det - 16.0).abs() < 1e-10, "det = {}", det);
}
#[test]
fn test_packed_ldlt_solve_lower() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0], &[2.0, 5.0]]);
let b: Mat<f64> = Mat::from_rows(&[&[8.0], &[11.0]]);
let ap = dense_to_packed_lower(a.as_ref());
let ldlt = PackedLdlt::compute(&ap, 2, Uplo::Lower).unwrap();
let x = ldlt.solve(b.as_ref()).unwrap();
let ax0 = 4.0 * x[(0, 0)] + 2.0 * x[(1, 0)];
let ax1 = 2.0 * x[(0, 0)] + 5.0 * x[(1, 0)];
assert!((ax0 - 8.0).abs() < 1e-10, "Ax[0] = {}", ax0);
assert!((ax1 - 11.0).abs() < 1e-10, "Ax[1] = {}", ax1);
}
#[test]
fn test_packed_ldlt_solve_upper() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0], &[2.0, 5.0]]);
let b: Mat<f64> = Mat::from_rows(&[&[8.0], &[11.0]]);
let ap = dense_to_packed_upper(a.as_ref());
let ldlt = PackedLdlt::compute(&ap, 2, Uplo::Upper).unwrap();
let x = ldlt.solve(b.as_ref()).unwrap();
let ax0 = 4.0 * x[(0, 0)] + 2.0 * x[(1, 0)];
let ax1 = 2.0 * x[(0, 0)] + 5.0 * x[(1, 0)];
assert!((ax0 - 8.0).abs() < 1e-10, "Ax[0] = {}", ax0);
assert!((ax1 - 11.0).abs() < 1e-10, "Ax[1] = {}", ax1);
}
#[test]
fn test_packed_ldlt_3x3() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0, 1.0], &[2.0, 5.0, 3.0], &[1.0, 3.0, 6.0]]);
let b: Mat<f64> = Mat::from_rows(&[&[1.0], &[2.0], &[3.0]]);
let ap = dense_to_packed_lower(a.as_ref());
let ldlt = PackedLdlt::compute(&ap, 3, Uplo::Lower).unwrap();
let x = ldlt.solve(b.as_ref()).unwrap();
let ax0 = 4.0 * x[(0, 0)] + 2.0 * x[(1, 0)] + 1.0 * x[(2, 0)];
let ax1 = 2.0 * x[(0, 0)] + 5.0 * x[(1, 0)] + 3.0 * x[(2, 0)];
let ax2 = 1.0 * x[(0, 0)] + 3.0 * x[(1, 0)] + 6.0 * x[(2, 0)];
assert!((ax0 - 1.0).abs() < 1e-10, "Ax[0] = {}", ax0);
assert!((ax1 - 2.0).abs() < 1e-10, "Ax[1] = {}", ax1);
assert!((ax2 - 3.0).abs() < 1e-10, "Ax[2] = {}", ax2);
}
#[test]
fn test_ppsv() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0], &[2.0, 5.0]]);
let b: Mat<f64> = Mat::from_rows(&[&[8.0], &[11.0]]);
let ap = dense_to_packed_lower(a.as_ref());
let x = ppsv(&ap, 2, Uplo::Lower, b.as_ref()).unwrap();
let ax0 = 4.0 * x[(0, 0)] + 2.0 * x[(1, 0)];
let ax1 = 2.0 * x[(0, 0)] + 5.0 * x[(1, 0)];
assert!((ax0 - 8.0).abs() < 1e-10);
assert!((ax1 - 11.0).abs() < 1e-10);
}
#[test]
fn test_spsv() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0], &[2.0, 5.0]]);
let b: Mat<f64> = Mat::from_rows(&[&[8.0], &[11.0]]);
let ap = dense_to_packed_lower(a.as_ref());
let x = spsv(&ap, 2, Uplo::Lower, b.as_ref()).unwrap();
let ax0 = 4.0 * x[(0, 0)] + 2.0 * x[(1, 0)];
let ax1 = 2.0 * x[(0, 0)] + 5.0 * x[(1, 0)];
assert!((ax0 - 8.0).abs() < 1e-10);
assert!((ax1 - 11.0).abs() < 1e-10);
}
#[test]
fn test_packed_multiple_rhs() {
let a: Mat<f64> = Mat::from_rows(&[&[4.0, 2.0], &[2.0, 5.0]]);
let b: Mat<f64> = Mat::from_rows(&[&[8.0, 1.0], &[11.0, 2.0]]);
let ap = dense_to_packed_lower(a.as_ref());
let chol = PackedCholesky::compute(&ap, 2, Uplo::Lower).unwrap();
let x = chol.solve(b.as_ref()).unwrap();
for col in 0..2 {
let ax0 = 4.0 * x[(0, col)] + 2.0 * x[(1, col)];
let ax1 = 2.0 * x[(0, col)] + 5.0 * x[(1, col)];
assert!((ax0 - b[(0, col)]).abs() < 1e-10);
assert!((ax1 - b[(1, col)]).abs() < 1e-10);
}
}
#[test]
fn test_packed_f32() {
let a: Mat<f32> = Mat::from_rows(&[&[4.0f32, 2.0], &[2.0, 5.0]]);
let b: Mat<f32> = Mat::from_rows(&[&[8.0f32], &[11.0]]);
let ap = dense_to_packed_lower(a.as_ref());
let chol = PackedCholesky::compute(&ap, 2, Uplo::Lower).unwrap();
let x = chol.solve(b.as_ref()).unwrap();
let ax0 = 4.0 * x[(0, 0)] + 2.0 * x[(1, 0)];
let ax1 = 2.0 * x[(0, 0)] + 5.0 * x[(1, 0)];
assert!((ax0 - 8.0).abs() < 1e-5);
assert!((ax1 - 11.0).abs() < 1e-5);
}
#[test]
fn test_packed_empty() {
let ap: Vec<f64> = vec![];
let chol = PackedCholesky::compute(&ap, 0, Uplo::Lower).unwrap();
assert_eq!(chol.size(), 0);
assert_eq!(chol.determinant(), 1.0);
let ldlt = PackedLdlt::compute(&ap, 0, Uplo::Lower).unwrap();
assert_eq!(ldlt.size(), 0);
assert_eq!(ldlt.determinant(), 1.0);
}
}