use num_traits::FromPrimitive;
use oxiblas_core::scalar::{Field, Real, Scalar};
use oxiblas_matrix::{Mat, MatRef};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AasenError {
EmptyMatrix,
NotSquare {
nrows: usize,
ncols: usize,
},
Singular {
index: usize,
},
DimensionMismatch {
expected: usize,
actual: usize,
},
TridiagonalSolveError,
}
impl core::fmt::Display for AasenError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::EmptyMatrix => write!(f, "Matrix is empty"),
Self::NotSquare { nrows, ncols } => {
write!(f, "Matrix is not square: {nrows}×{ncols}")
}
Self::Singular { index } => {
write!(f, "Matrix is singular at index {index}")
}
Self::DimensionMismatch { expected, actual } => {
write!(f, "Dimension mismatch: expected {expected}, got {actual}")
}
Self::TridiagonalSolveError => {
write!(f, "Error solving tridiagonal system")
}
}
}
}
impl std::error::Error for AasenError {}
#[derive(Clone, Debug)]
pub struct Aasen<T: Scalar> {
l: Mat<T>,
t_diag: Vec<T>,
t_subdiag: Vec<T>,
piv: Vec<usize>,
n: usize,
}
impl<T: Field + Real + bytemuck::Zeroable + FromPrimitive> Aasen<T> {
pub fn compute(a: MatRef<'_, T>) -> Result<Self, AasenError> {
let n = a.nrows();
if n == 0 {
return Err(AasenError::EmptyMatrix);
}
if n != a.ncols() {
return Err(AasenError::NotSquare {
nrows: n,
ncols: a.ncols(),
});
}
if n == 1 {
return Ok(Self {
l: Mat::eye(1),
t_diag: vec![a[(0, 0)]],
t_subdiag: vec![],
piv: vec![0],
n: 1,
});
}
let mut a_work = Mat::zeros(n, n);
for i in 0..n {
for j in 0..=i {
let v = a[(i, j)];
a_work[(i, j)] = v;
a_work[(j, i)] = v;
}
}
let mut l = Mat::<T>::eye(n);
let mut t_diag = vec![T::zero(); n];
let mut t_subdiag = vec![T::zero(); n - 1];
let mut piv: Vec<usize> = (0..n).collect();
let mut hcol = vec![T::zero(); n];
let mut wprime = vec![T::zero(); n];
let mut wvec = vec![T::zero(); n];
for j in 0..n {
for k in 0..j {
let mut hkj = t_diag[k] * l[(j, k)] + t_subdiag[k] * l[(j, k + 1)];
if k >= 1 {
hkj += t_subdiag[k - 1] * l[(j, k - 1)];
}
hcol[k] = hkj;
}
for i in j..n {
let mut s = a_work[(i, j)];
for k in 0..j {
s -= l[(i, k)] * hcol[k];
}
wprime[i] = s;
}
let h_jj = wprime[j];
let mut d_jj = h_jj;
if j >= 1 {
d_jj -= t_subdiag[j - 1] * l[(j, j - 1)];
}
t_diag[j] = d_jj;
if j + 1 >= n {
continue;
}
for i in (j + 1)..n {
wvec[i] = wprime[i] - l[(i, j)] * h_jj;
}
let mut p = j + 1;
let mut pmax = Scalar::abs(wvec[j + 1]);
for i in (j + 2)..n {
let cand = Scalar::abs(wvec[i]);
if cand > pmax {
pmax = cand;
p = i;
}
}
if p != j + 1 {
Self::swap_sym(&mut a_work, j + 1, p, n);
for k in 0..=j {
let tmp = l[(j + 1, k)];
l[(j + 1, k)] = l[(p, k)];
l[(p, k)] = tmp;
}
wvec.swap(j + 1, p);
}
piv[j + 1] = p;
let e_j = wvec[j + 1];
t_subdiag[j] = e_j;
if e_j != T::zero() {
for i in (j + 2)..n {
l[(i, j + 1)] = wvec[i] / e_j;
}
}
}
Ok(Self {
l,
t_diag,
t_subdiag,
piv,
n,
})
}
fn swap_sym(a: &mut Mat<T>, i: usize, j: usize, n: usize) {
if i == j {
return;
}
for k in 0..n {
let tmp = a[(i, k)];
a[(i, k)] = a[(j, k)];
a[(j, k)] = tmp;
}
for k in 0..n {
let tmp = a[(k, i)];
a[(k, i)] = a[(k, j)];
a[(k, j)] = tmp;
}
}
pub fn solve(&self, b: MatRef<'_, T>) -> Result<Mat<T>, AasenError> {
if b.nrows() != self.n {
return Err(AasenError::DimensionMismatch {
expected: self.n,
actual: b.nrows(),
});
}
let nrhs = b.ncols();
let n = self.n;
let mut x = Mat::zeros(n, nrhs);
for i in 0..n {
for j in 0..nrhs {
x[(i, j)] = b[(i, j)];
}
}
for k in 0..n {
let pk = self.piv[k];
if pk != k {
for j in 0..nrhs {
let tmp = x[(k, j)];
x[(k, j)] = x[(pk, j)];
x[(pk, j)] = tmp;
}
}
}
for k in 0..n {
for i in (k + 1)..n {
let lik = self.l[(i, k)];
if lik != T::zero() {
for j in 0..nrhs {
let xk = x[(k, j)];
x[(i, j)] -= lik * xk;
}
}
}
}
self.solve_tridiagonal(&mut x, nrhs)?;
for k in (0..n).rev() {
for i in (k + 1)..n {
let lik = self.l[(i, k)];
if lik != T::zero() {
for j in 0..nrhs {
let xi = x[(i, j)];
x[(k, j)] -= lik * xi;
}
}
}
}
for k in (0..n).rev() {
let pk = self.piv[k];
if pk != k {
for j in 0..nrhs {
let tmp = x[(k, j)];
x[(k, j)] = x[(pk, j)];
x[(pk, j)] = tmp;
}
}
}
Ok(x)
}
fn solve_tridiagonal(&self, x: &mut Mat<T>, nrhs: usize) -> Result<(), AasenError> {
let n = self.n;
if n == 0 {
return Ok(());
}
if n == 1 {
let d0 = self.t_diag[0];
if d0 == T::zero() {
return Err(AasenError::Singular { index: 0 });
}
for j in 0..nrhs {
x[(0, j)] = x[(0, j)] / d0;
}
return Ok(());
}
let mut d = self.t_diag.clone();
let mut du = self.t_subdiag.clone();
let dl = self.t_subdiag.clone();
let mut du2 = vec![T::zero(); n];
for i in 0..(n - 1) {
if Scalar::abs(d[i]) >= Scalar::abs(dl[i]) {
if d[i] == T::zero() {
return Err(AasenError::Singular { index: i });
}
let fact = dl[i] / d[i];
d[i + 1] -= fact * du[i];
for j in 0..nrhs {
let xi = x[(i, j)];
x[(i + 1, j)] -= fact * xi;
}
} else {
let fact = d[i] / dl[i];
d[i] = dl[i];
let temp = d[i + 1];
d[i + 1] = du[i] - fact * temp;
du[i] = temp;
if i < n - 2 {
du2[i] = du[i + 1];
du[i + 1] = -fact * du2[i];
}
for j in 0..nrhs {
let temp_b = x[(i, j)];
let xip = x[(i + 1, j)];
x[(i, j)] = xip;
x[(i + 1, j)] = temp_b - fact * xip;
}
}
}
if d[n - 1] == T::zero() {
return Err(AasenError::Singular { index: n - 1 });
}
for j in 0..nrhs {
x[(n - 1, j)] = x[(n - 1, j)] / d[n - 1];
let val = x[(n - 2, j)] - du[n - 2] * x[(n - 1, j)];
x[(n - 2, j)] = val / d[n - 2];
for i in (0..(n - 2)).rev() {
let val = x[(i, j)] - du[i] * x[(i + 1, j)] - du2[i] * x[(i + 2, j)];
x[(i, j)] = val / d[i];
}
}
Ok(())
}
pub fn l_factor(&self) -> &Mat<T> {
&self.l
}
pub fn t_diagonal(&self) -> &[T] {
&self.t_diag
}
pub fn t_subdiagonal(&self) -> &[T] {
&self.t_subdiag
}
pub fn pivot(&self) -> &[usize] {
&self.piv
}
pub fn n(&self) -> usize {
self.n
}
pub fn t_matrix(&self) -> Mat<T> {
let n = self.n;
let mut t = Mat::zeros(n, n);
for i in 0..n {
t[(i, i)] = self.t_diag[i];
if i + 1 < n {
t[(i, i + 1)] = self.t_subdiag[i];
t[(i + 1, i)] = self.t_subdiag[i];
}
}
t
}
pub fn inertia(&self) -> (usize, usize, usize) {
let n = self.n;
if n == 0 {
return (0, 0, 0);
}
let mut a = self.t_diag.clone();
let b = &self.t_subdiag;
let mut anorm = T::zero();
for i in 0..n {
let mut s = Scalar::abs(a[i]);
if i > 0 {
s += Scalar::abs(b[i - 1]);
}
if i + 1 < n {
s += Scalar::abs(b[i]);
}
if s > anorm {
anorm = s;
}
}
let two = T::one() + T::one();
let eight = two * two * two;
let eps = <T as Scalar>::epsilon();
let tol = eps * eight * anorm; let det_tol = tol * anorm;
let zero = T::zero();
let mut n_pos = 0usize;
let mut n_neg = 0usize;
let mut n_zero = 0usize;
let mut i = 0usize;
while i < n {
let a_i = a[i];
let last = i + 1 >= n;
let b_i = if last { zero } else { b[i] };
if Scalar::abs(a_i) > tol {
if a_i > zero {
n_pos += 1;
} else {
n_neg += 1;
}
if !last {
a[i + 1] -= b_i * b_i / a_i;
}
i += 1;
} else if !last && Scalar::abs(b_i) > tol {
let a_ip = a[i + 1];
let det2 = a_i * a_ip - b_i * b_i;
let trace = a_i + a_ip;
if Scalar::abs(det2) <= det_tol {
n_zero += 1;
if Scalar::abs(trace) > tol {
if trace > zero {
n_pos += 1;
} else {
n_neg += 1;
}
} else {
n_zero += 1;
}
} else if det2 < zero {
n_pos += 1;
n_neg += 1;
} else if trace > zero {
n_pos += 2;
} else {
n_neg += 2;
}
if i + 2 < n {
let b_next = b[i + 1];
a[i + 2] -= b_next * b_next * a_i / det2;
}
i += 2;
} else {
n_zero += 1;
i += 1;
}
}
(n_pos, n_neg, n_zero)
}
}
pub fn aasen<T: Field + Real + bytemuck::Zeroable + FromPrimitive>(
a: MatRef<'_, T>,
) -> Result<Aasen<T>, AasenError> {
Aasen::compute(a)
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
fn check_factorization(rows: &[&[f64]]) {
let n = rows.len();
let a = Mat::from_rows(rows);
let aasen = Aasen::compute(a.as_ref()).expect("compute should succeed");
let l = aasen.l_factor();
for i in 0..n {
assert!(
approx_eq(l[(i, i)], 1.0, 1e-12),
"L must be unit diagonal: L[{i},{i}] = {}",
l[(i, i)]
);
}
for i in 1..n {
assert!(
approx_eq(l[(i, 0)], 0.0, 1e-12),
"first column of L must be e_0: L[{i},0] = {}",
l[(i, 0)]
);
}
for i in 0..n {
for j in (i + 1)..n {
assert!(
approx_eq(l[(i, j)], 0.0, 1e-12),
"L must be lower triangular: L[{i},{j}] = {}",
l[(i, j)]
);
}
for j in 0..i {
assert!(
l[(i, j)].abs() <= 1.0 + 1e-9,
"Aasen multiplier bound violated: |L[{i},{j}]| = {}",
l[(i, j)].abs()
);
}
}
let t = aasen.t_matrix();
for i in 0..n {
for j in 0..n {
if i != j && (i as isize - j as isize).abs() != 1 {
assert!(
approx_eq(t[(i, j)], 0.0, 1e-12),
"T must be tridiagonal: T[{i},{j}] = {}",
t[(i, j)]
);
}
assert!(
approx_eq(t[(i, j)], t[(j, i)], 1e-12),
"T must be symmetric at [{i},{j}]"
);
}
}
let mut lt = vec![vec![0.0f64; n]; n];
for (i, lt_row) in lt.iter_mut().enumerate() {
for (j, lt_ij) in lt_row.iter_mut().enumerate() {
let mut s = 0.0;
for k in 0..n {
s += l[(i, k)] * t[(k, j)];
}
*lt_ij = s;
}
}
let mut ltlt = vec![vec![0.0f64; n]; n];
for (i, ltlt_row) in ltlt.iter_mut().enumerate() {
for (j, ltlt_ij) in ltlt_row.iter_mut().enumerate() {
let mut s = 0.0;
for k in 0..n {
s += lt[i][k] * l[(j, k)];
}
*ltlt_ij = s;
}
}
let piv = aasen.pivot();
let mut pa = vec![vec![0.0f64; n]; n];
for (i, pa_row) in pa.iter_mut().enumerate() {
for (j, pa_ij) in pa_row.iter_mut().enumerate() {
*pa_ij = a[(i, j)];
}
}
for k in 1..n {
let p = piv[k];
if p != k {
pa.swap(k, p);
for row in pa.iter_mut() {
row.swap(k, p);
}
}
}
for i in 0..n {
for j in 0..n {
assert!(
approx_eq(ltlt[i][j], pa[i][j], 1e-8),
"reconstruction mismatch at [{i},{j}]: LTL^T = {}, PAP^T = {}",
ltlt[i][j],
pa[i][j]
);
}
}
let x_true: Vec<f64> = (0..n).map(|i| 1.0 + 0.5 * i as f64).collect();
let mut b = Mat::zeros(n, 1);
for i in 0..n {
let mut s = 0.0;
for j in 0..n {
s += a[(i, j)] * x_true[j];
}
b[(i, 0)] = s;
}
let x = aasen.solve(b.as_ref()).expect("solve should succeed");
for i in 0..n {
assert!(
approx_eq(x[(i, 0)], x_true[i], 1e-6),
"solve mismatch at {i}: x = {}, expected {}",
x[(i, 0)],
x_true[i]
);
}
}
#[test]
fn test_aasen_2x2() {
let a = Mat::from_rows(&[&[4.0f64, 2.0], &[2.0, 5.0]]);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
let b = Mat::from_rows(&[&[6.0], &[7.0]]);
let x = aasen.solve(b.as_ref()).expect("solve");
let ax0 = a[(0, 0)] * x[(0, 0)] + a[(0, 1)] * x[(1, 0)];
let ax1 = a[(1, 0)] * x[(0, 0)] + a[(1, 1)] * x[(1, 0)];
assert!(approx_eq(ax0, b[(0, 0)], 1e-10), "ax0 = {ax0}");
assert!(approx_eq(ax1, b[(1, 0)], 1e-10), "ax1 = {ax1}");
}
#[test]
fn test_aasen_diagonal() {
let a = Mat::from_rows(&[&[2.0f64, 0.0, 0.0], &[0.0, 3.0, 0.0], &[0.0, 0.0, 4.0]]);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
let b = Mat::from_rows(&[&[2.0], &[6.0], &[12.0]]);
let x = aasen.solve(b.as_ref()).expect("solve");
assert!(approx_eq(x[(0, 0)], 1.0, 1e-10));
assert!(approx_eq(x[(1, 0)], 2.0, 1e-10));
assert!(approx_eq(x[(2, 0)], 3.0, 1e-10));
}
#[test]
fn test_aasen_identity() {
let a: Mat<f64> = Mat::eye(4);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
let b = Mat::from_rows(&[&[1.0], &[2.0], &[3.0], &[4.0]]);
let x = aasen.solve(b.as_ref()).expect("solve");
for i in 0..4 {
assert!(approx_eq(x[(i, 0)], b[(i, 0)], 1e-10));
}
}
#[test]
fn test_aasen_multiple_rhs() {
let a = Mat::from_rows(&[&[4.0f64, 2.0], &[2.0, 5.0]]);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
let b = Mat::from_rows(&[&[1.0, 4.0], &[2.0, 5.0]]);
let x = aasen.solve(b.as_ref()).expect("solve");
for col in 0..2 {
for i in 0..2 {
let mut ax_i = 0.0;
for j in 0..2 {
ax_i += a[(i, j)] * x[(j, col)];
}
assert!(
approx_eq(ax_i, b[(i, col)], 1e-10),
"Ax[{i},{col}] = {ax_i}, b = {}",
b[(i, col)]
);
}
}
}
#[test]
fn test_aasen_t_matrix() {
let a = Mat::from_rows(&[&[4.0f64, 2.0, 1.0], &[2.0, 5.0, 2.0], &[1.0, 2.0, 6.0]]);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
let t = aasen.t_matrix();
for i in 0..3 {
for j in 0..3 {
if !(i == j || (i as i32 - j as i32).abs() == 1) {
assert!(
approx_eq(t[(i, j)], 0.0, 1e-10),
"T[{i},{j}] = {} should be 0",
t[(i, j)]
);
}
}
}
for i in 0..3 {
for j in 0..3 {
assert!(
approx_eq(t[(i, j)], t[(j, i)], 1e-10),
"T not symmetric at [{i},{j}]"
);
}
}
}
#[test]
fn test_aasen_error_not_square() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 3.0], &[4.0, 5.0, 6.0]]);
let result = Aasen::compute(a.as_ref());
assert!(matches!(result, Err(AasenError::NotSquare { .. })));
}
#[test]
fn test_aasen_error_empty() {
let a: Mat<f64> = Mat::zeros(0, 0);
let result = Aasen::compute(a.as_ref());
assert!(matches!(result, Err(AasenError::EmptyMatrix)));
}
#[test]
fn test_aasen_1x1() {
let a = Mat::from_rows(&[&[5.0f64]]);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
let b = Mat::from_rows(&[&[10.0]]);
let x = aasen.solve(b.as_ref()).expect("solve");
assert!(approx_eq(x[(0, 0)], 2.0, 1e-10));
}
#[test]
fn test_aasen_3x3_general() {
check_factorization(&[&[1.0, 2.0, 4.0], &[2.0, 3.0, 5.0], &[4.0, 5.0, 6.0]]);
}
#[test]
fn test_aasen_3x3_zero_diagonal_pivot() {
check_factorization(&[&[0.0, 2.0, 1.0], &[2.0, 0.0, 3.0], &[1.0, 3.0, 0.0]]);
}
#[test]
fn test_aasen_3x3_spd() {
check_factorization(&[&[4.0, 2.0, 1.0], &[2.0, 5.0, 2.0], &[1.0, 2.0, 6.0]]);
}
#[test]
fn test_aasen_4x4_indefinite() {
check_factorization(&[
&[1.0, 3.0, -2.0, 4.0],
&[3.0, -5.0, 1.0, 2.0],
&[-2.0, 1.0, 6.0, -1.0],
&[4.0, 2.0, -1.0, 3.0],
]);
}
#[test]
fn test_aasen_4x4_repeated_eigenvalues() {
check_factorization(&[
&[0.0, 1.0, 0.0, 0.0],
&[1.0, 0.0, 0.0, 0.0],
&[0.0, 0.0, 0.0, 1.0],
&[0.0, 0.0, 1.0, 0.0],
]);
}
fn dominant_indefinite(n: usize, seed: usize) -> Vec<Vec<f64>> {
let mut rows = vec![vec![0.0f64; n]; n];
for i in 0..n {
for j in 0..i {
let v = (((i * seed + j * 3 + 1) % 9) as f64) - 4.0;
rows[i][j] = v;
rows[j][i] = v;
}
}
for i in 0..n {
let mut off = 0.0;
for j in 0..n {
if j != i {
off += rows[i][j].abs();
}
}
let sign = if i % 2 == 0 { 1.0 } else { -1.0 };
rows[i][i] = sign * (off + 2.0);
}
rows
}
#[test]
fn test_aasen_5x5_indefinite() {
let rows = dominant_indefinite(5, 7);
let refs: Vec<&[f64]> = rows.iter().map(|r| r.as_slice()).collect();
check_factorization(&refs);
}
#[test]
fn test_aasen_8x8_indefinite() {
let rows = dominant_indefinite(8, 5);
let refs: Vec<&[f64]> = rows.iter().map(|r| r.as_slice()).collect();
check_factorization(&refs);
}
#[test]
fn test_aasen_solve_multiple_rhs_3x3() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 4.0], &[2.0, 3.0, 5.0], &[4.0, 5.0, 6.0]]);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
let b = Mat::from_rows(&[&[7.0, 1.0], &[10.0, 0.0], &[15.0, -2.0]]);
let x = aasen.solve(b.as_ref()).expect("solve");
for col in 0..2 {
for i in 0..3 {
let mut ax = 0.0;
for j in 0..3 {
ax += a[(i, j)] * x[(j, col)];
}
assert!(
approx_eq(ax, b[(i, col)], 1e-9),
"Ax[{i},{col}] = {ax}, b = {}",
b[(i, col)]
);
}
}
}
#[test]
fn test_aasen_f32() {
let a = Mat::from_rows(&[&[2.0f32, 1.0, 0.0], &[1.0, -3.0, 2.0], &[0.0, 2.0, 1.0]]);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
let b = Mat::from_rows(&[&[3.0f32], &[0.0], &[3.0]]);
let x = aasen.solve(b.as_ref()).expect("solve");
for i in 0..3 {
let mut ax = 0.0f32;
for j in 0..3 {
ax += a[(i, j)] * x[(j, 0)];
}
assert!(
approx_eq(ax as f64, b[(i, 0)] as f64, 1e-4),
"Ax[{i}] = {ax}"
);
}
}
#[test]
fn test_aasen_singular_solve_reports_error() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 3.0], &[2.0, 4.0, 6.0], &[3.0, 6.0, 9.0]]);
let aasen = Aasen::compute(a.as_ref()).expect("factorization still completes");
let b = Mat::from_rows(&[&[1.0], &[2.0], &[3.0]]);
let result = aasen.solve(b.as_ref());
assert!(
matches!(result, Err(AasenError::Singular { .. })),
"singular T must be reported, got {result:?}"
);
}
#[test]
fn test_aasen_nan_propagates() {
let a = Mat::from_rows(&[&[f64::NAN, 1.0, 0.0], &[1.0, 2.0, 1.0], &[0.0, 1.0, 3.0]]);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
let b = Mat::from_rows(&[&[1.0], &[1.0], &[1.0]]);
if let Ok(x) = aasen.solve(b.as_ref()) {
let any_nan = (0..3).any(|i| x[(i, 0)].is_nan());
assert!(any_nan, "NaN in the matrix must propagate to the solution");
}
}
#[test]
fn test_aasen_dimension_mismatch() {
let a = Mat::from_rows(&[&[1.0f64, 2.0], &[2.0, 3.0]]);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
let b = Mat::from_rows(&[&[1.0], &[2.0], &[3.0]]);
let result = aasen.solve(b.as_ref());
assert!(matches!(result, Err(AasenError::DimensionMismatch { .. })));
}
#[test]
fn test_inertia_positive_definite() {
let a = Mat::from_rows(&[&[4.0f64, 2.0, 1.0], &[2.0, 5.0, 2.0], &[1.0, 2.0, 6.0]]);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
assert_eq!(aasen.inertia(), (3, 0, 0));
}
#[test]
fn test_inertia_indefinite() {
let a = Mat::from_rows(&[&[1.0f64, 2.0, 4.0], &[2.0, 3.0, 5.0], &[4.0, 5.0, 6.0]]);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
assert_eq!(aasen.inertia(), (1, 2, 0));
}
#[test]
fn test_inertia_zero_diagonal_block() {
let a = Mat::from_rows(&[&[0.0f64, 1.0], &[1.0, 0.0]]);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
assert_eq!(aasen.inertia(), (1, 1, 0));
}
#[test]
fn test_inertia_diagonal_signs() {
let a = Mat::from_rows(&[&[2.0f64, 0.0, 0.0], &[0.0, 3.0, 0.0], &[0.0, 0.0, -1.0]]);
let aasen = Aasen::compute(a.as_ref()).expect("compute");
assert_eq!(aasen.inertia(), (2, 1, 0));
}
}