#[cfg(test)]
mod tests {
fn vec_inf_norm(v: &[f32]) -> f32 {
v.iter().map(|x| x.abs()).fold(0.0_f32, f32::max)
}
fn dense_matvec(a: &[f32], x: &[f32], rows: usize, cols: usize) -> Vec<f32> {
let mut y = vec![0.0_f32; rows];
for i in 0..rows {
for j in 0..cols {
y[i] += a[i * cols + j] * x[j];
}
}
y
}
fn dense_matmul(a: &[f32], b: &[f32], m: usize, k: usize, n: usize) -> Vec<f32> {
let mut c = vec![0.0_f32; m * n];
for i in 0..m {
for j in 0..n {
for p in 0..k {
c[i * n + j] += a[i * k + p] * b[p * n + j];
}
}
}
c
}
fn is_symmetric(m: &[f32], n: usize, tol: f32) -> bool {
for i in 0..n {
for j in (i + 1)..n {
if (m[i * n + j] - m[j * n + i]).abs() > tol {
return false;
}
}
}
true
}
fn mat_inf_norm(a: &[f32], rows: usize, cols: usize) -> f32 {
let mut max = 0.0_f32;
for i in 0..rows {
let row_sum: f32 = (0..cols).map(|j| a[i * cols + j].abs()).sum();
if row_sum > max {
max = row_sum;
}
}
max
}
use trueno_sparse::{BsrMatrix, CooMatrix, CsrMatrix, SellMatrix, SparseOps};
#[test]
fn test_bsr_to_csr_roundtrip() {
#[rustfmt::skip]
let dense = vec![
1.0, 2.0, 0.0, 0.0,
3.0, 4.0, 0.0, 0.0,
0.0, 0.0, 5.0, 6.0,
0.0, 0.0, 7.0, 8.0,
];
let bsr = BsrMatrix::from_dense(&dense, 4, 4, 2);
assert_eq!(bsr.rows(), 4);
assert_eq!(bsr.cols(), 4);
let csr = bsr.to_csr().expect("BSR->CSR should succeed");
assert_eq!(csr.rows(), 4);
assert_eq!(csr.cols(), 4);
let recovered = csr.to_dense();
for i in 0..16 {
assert!(
(dense[i] - recovered[i]).abs() < 1e-6,
"Mismatch at index {}: expected {}, got {}",
i,
dense[i],
recovered[i]
);
}
let bsr2 = BsrMatrix::from_dense(&recovered, 4, 4, 2);
let csr2 = bsr2.to_csr().expect("second BSR->CSR");
let recovered2 = csr2.to_dense();
for i in 0..16 {
assert!(
(recovered[i] - recovered2[i]).abs() < 1e-6,
"Round-trip mismatch at index {}",
i
);
}
}
#[test]
fn test_bsr_alpha_beta() {
#[rustfmt::skip]
let dense = vec![
2.0, 1.0,
1.0, 3.0,
];
let bsr = BsrMatrix::from_dense(&dense, 2, 2, 2);
let x = vec![1.0, 2.0];
let mut y = vec![10.0, 20.0];
let alpha = 2.0;
let beta = 0.5;
bsr.spmv(alpha, &x, beta, &mut y).unwrap();
assert!((y[0] - 13.0).abs() < 1e-5, "y[0] = {}, expected 13.0", y[0]);
assert!((y[1] - 24.0).abs() < 1e-5, "y[1] = {}, expected 24.0", y[1]);
}
#[test]
fn test_sell_matches_csr() {
let coo = CooMatrix::new(
4,
4,
vec![0, 0, 1, 2, 2, 3],
vec![0, 2, 1, 0, 2, 3],
vec![1.0_f32, 3.0, 5.0, 2.0, 4.0, 6.0],
)
.unwrap();
let csr = CsrMatrix::from_coo(&coo);
let sell = SellMatrix::from_csr(&csr, 2);
let x = vec![1.0_f32, 2.0, 3.0, 4.0];
let mut y_csr = vec![0.0_f32; 4];
csr.spmv(1.0, &x, 0.0, &mut y_csr).unwrap();
let mut y_sell = vec![0.0_f32; 4];
sell.spmv(1.0, &x, 0.0, &mut y_sell).unwrap();
for i in 0..4 {
assert!(
(y_csr[i] - y_sell[i]).abs() < 1e-5,
"Mismatch at {}: CSR={}, SELL={}",
i,
y_csr[i],
y_sell[i]
);
}
}
#[test]
fn test_sell_spmv_dimension_mismatch() {
let csr = CsrMatrix::identity(3);
let sell = SellMatrix::from_csr(&csr, 2);
let x = vec![1.0_f32; 5]; let mut y = vec![0.0_f32; 3];
let result = sell.spmv(1.0, &x, 0.0, &mut y);
assert!(result.is_err(), "Should fail on dimension mismatch");
}
#[test]
fn test_sell_spmv_alpha_beta() {
let csr = CsrMatrix::identity(3);
let sell = SellMatrix::from_csr(&csr, 2);
let x = vec![1.0_f32, 2.0, 3.0];
let mut y = vec![10.0_f32, 20.0, 30.0];
sell.spmv(2.0, &x, 0.5, &mut y).unwrap();
assert!((y[0] - 7.0).abs() < 1e-5);
assert!((y[1] - 14.0).abs() < 1e-5);
assert!((y[2] - 21.0).abs() < 1e-5);
}
#[test]
fn test_spgemm_identity() {
let coo =
CooMatrix::new(3, 3, vec![0, 1, 2, 0], vec![0, 1, 2, 2], vec![2.0_f32, 3.0, 4.0, 5.0])
.unwrap();
let a = CsrMatrix::from_coo(&coo);
let identity = CsrMatrix::<f32>::identity(3);
let c = trueno_sparse::spgemm(&a, &identity).unwrap();
let a_dense = a.to_dense();
let c_dense = c.to_dense();
for i in 0..9 {
assert!(
(a_dense[i] - c_dense[i]).abs() < 1e-5,
"A*I mismatch at {}: A={}, C={}",
i,
a_dense[i],
c_dense[i]
);
}
}
#[test]
fn test_spgemm_identity_left() {
let coo =
CooMatrix::new(3, 3, vec![0, 1, 2, 0], vec![0, 1, 2, 2], vec![2.0_f32, 3.0, 4.0, 5.0])
.unwrap();
let a = CsrMatrix::from_coo(&coo);
let identity = CsrMatrix::<f32>::identity(3);
let c = trueno_sparse::spgemm(&identity, &a).unwrap();
let a_dense = a.to_dense();
let c_dense = c.to_dense();
for i in 0..9 {
assert!(
(a_dense[i] - c_dense[i]).abs() < 1e-5,
"I*A mismatch at {}: A={}, C={}",
i,
a_dense[i],
c_dense[i]
);
}
}
#[test]
fn test_spgemm_known_product() {
let coo_a =
CooMatrix::new(2, 2, vec![0, 0, 1], vec![0, 1, 1], vec![1.0_f32, 2.0, 3.0]).unwrap();
let coo_b =
CooMatrix::new(2, 2, vec![0, 1, 1], vec![0, 0, 1], vec![4.0_f32, 1.0, 2.0]).unwrap();
let a = CsrMatrix::from_coo(&coo_a);
let b = CsrMatrix::from_coo(&coo_b);
let c = trueno_sparse::spgemm(&a, &b).unwrap();
let c_dense = c.to_dense();
let expected = vec![6.0, 4.0, 3.0, 6.0];
for i in 0..4 {
assert!(
(c_dense[i] - expected[i]).abs() < 1e-5,
"Product mismatch at {}: got {}, expected {}",
i,
c_dense[i],
expected[i]
);
}
}
#[test]
fn test_spgemm_dimension_mismatch() {
let a = CsrMatrix::new(2, 3, vec![0, 1, 2], vec![0, 1], vec![1.0_f32, 2.0]).unwrap();
let b = CsrMatrix::<f32>::identity(2);
let result = trueno_sparse::spgemm(&a, &b);
assert!(result.is_err(), "Should fail: A.cols != B.rows");
}
#[test]
fn test_spgemm_sparse_result() {
let a =
CsrMatrix::new(3, 3, vec![0, 1, 2, 3], vec![0, 1, 2], vec![1.0_f32, 2.0, 3.0]).unwrap();
let b =
CsrMatrix::new(3, 3, vec![0, 1, 2, 3], vec![0, 1, 2], vec![4.0_f32, 5.0, 6.0]).unwrap();
let c = trueno_sparse::spgemm(&a, &b).unwrap();
assert_eq!(c.nnz(), 3, "Product of diagonal matrices should have 3 nnz");
let c_dense = c.to_dense();
assert!((c_dense[0] - 4.0).abs() < 1e-5);
assert!((c_dense[4] - 10.0).abs() < 1e-5);
assert!((c_dense[8] - 18.0).abs() < 1e-5);
assert!(c_dense[1].abs() < 1e-5);
assert!(c_dense[3].abs() < 1e-5);
}
#[test]
fn test_spmm_matches_dense() {
let coo = CooMatrix::new(
3,
3,
vec![0, 0, 1, 2, 2],
vec![0, 1, 1, 0, 2],
vec![1.0_f32, 2.0, 3.0, 4.0, 5.0],
)
.unwrap();
let csr = CsrMatrix::from_coo(&coo);
let a_dense = csr.to_dense();
let b = vec![1.0_f32, 2.0, 3.0, 4.0, 5.0, 6.0];
let b_cols = 2;
let mut c_sparse = vec![0.0_f32; 3 * b_cols];
csr.spmm(1.0, &b, b_cols, 0.0, &mut c_sparse).unwrap();
let c_dense = dense_matmul(&a_dense, &b, 3, 3, b_cols);
for i in 0..6 {
assert!(
(c_sparse[i] - c_dense[i]).abs() < 1e-4,
"SpMM mismatch at {}: sparse={}, dense={}",
i,
c_sparse[i],
c_dense[i]
);
}
}
#[test]
fn test_spmm_dimension_mismatch() {
let csr = CsrMatrix::<f32>::identity(3);
let b = vec![1.0_f32; 4 * 2];
let mut c = vec![0.0_f32; 3 * 2];
let result = csr.spmm(1.0, &b, 2, 0.0, &mut c);
assert!(result.is_err(), "Should fail on dimension mismatch");
}
#[test]
fn test_spmv_known_result() {
let coo = CooMatrix::new(
3,
3,
vec![0, 0, 1, 2, 2],
vec![0, 2, 1, 0, 2],
vec![1.0_f32, 2.0, 3.0, 4.0, 5.0],
)
.unwrap();
let csr = CsrMatrix::from_coo(&coo);
let x = vec![1.0_f32, 2.0, 3.0];
let mut y = vec![0.0_f32; 3];
csr.spmv(1.0, &x, 0.0, &mut y).unwrap();
assert!((y[0] - 7.0).abs() < 1e-5, "y[0]={}, expected 7", y[0]);
assert!((y[1] - 6.0).abs() < 1e-5, "y[1]={}, expected 6", y[1]);
assert!((y[2] - 19.0).abs() < 1e-5, "y[2]={}, expected 19", y[2]);
}
#[test]
fn test_spmv_dimension_mismatch() {
let csr = CsrMatrix::<f32>::identity(3);
let x = vec![1.0_f32; 5]; let mut y = vec![0.0_f32; 3];
let result = csr.spmv(1.0, &x, 0.0, &mut y);
assert!(result.is_err());
}
#[test]
fn test_spmv_alpha_beta() {
let csr = CsrMatrix::<f32>::identity(3);
let x = vec![1.0_f32, 2.0, 3.0];
let mut y = vec![10.0_f32, 20.0, 30.0];
csr.spmv(2.0, &x, 0.5, &mut y).unwrap();
assert!((y[0] - 7.0).abs() < 1e-5);
assert!((y[1] - 14.0).abs() < 1e-5);
assert!((y[2] - 21.0).abs() < 1e-5);
}
#[test]
fn prop_spmv_backward_error() {
let coo = CooMatrix::new(
3,
3,
vec![0, 0, 0, 1, 1, 1, 2, 2, 2],
vec![0, 1, 2, 0, 1, 2, 0, 1, 2],
vec![1.0_f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
)
.unwrap();
let csr = CsrMatrix::from_coo(&coo);
let a_dense = csr.to_dense();
let x = vec![0.1_f32, 0.2, 0.3];
let mut y = vec![0.0_f32; 3];
csr.spmv(1.0, &x, 0.0, &mut y).unwrap();
let y_ref = dense_matvec(&a_dense, &x, 3, 3);
let residual: f32 =
y.iter().zip(y_ref.iter()).map(|(a, b)| (a - b).abs()).fold(0.0_f32, f32::max);
let a_norm = mat_inf_norm(&a_dense, 3, 3);
let x_norm = vec_inf_norm(&x);
let eps = 3.0 * f32::EPSILON * a_norm * x_norm;
assert!(residual < eps, "Backward error too large: residual={}, bound={}", residual, eps);
}
use trueno_solve::{
cholesky, lu_factorize, qr_factorize, svd, symm, syrk, trmm, trsm, DiagonalType,
TriangularSide,
};
#[test]
fn test_cholesky_spd_solve() {
let a = [4.0_f32, 2.0, 2.0, 3.0];
let b = [1.0_f32, 2.0];
let chol = cholesky(&a, 2).unwrap();
let x = chol.solve(&b).unwrap();
let ax = dense_matvec(&a, &x, 2, 2);
for i in 0..2 {
assert!(
(ax[i] - b[i]).abs() < 1e-4,
"Cholesky solve: Ax[{}]={}, b[{}]={}",
i,
ax[i],
i,
b[i]
);
}
}
#[test]
fn test_cholesky_non_spd() {
let a = [1.0_f32, 0.0, 0.0, -1.0];
let result = cholesky(&a, 2);
assert!(result.is_err(), "Should fail on non-SPD matrix");
}
#[test]
fn test_cholesky_residual() {
let a = [9.0_f32, 3.0, 3.0, 3.0, 5.0, 2.0, 3.0, 2.0, 4.0];
let b = [1.0_f32, 2.0, 3.0];
let chol = cholesky(&a, 3).unwrap();
let x = chol.solve(&b).unwrap();
let ax = dense_matvec(&a, &x, 3, 3);
let residual: Vec<f32> = ax.iter().zip(b.iter()).map(|(a, b)| a - b).collect();
let res_norm = vec_inf_norm(&residual);
let b_norm = vec_inf_norm(&b);
let relative_residual = res_norm / b_norm;
assert!(
relative_residual < 1e-4,
"Cholesky residual too large: ||Ax-b||/||b|| = {}",
relative_residual
);
}
#[test]
fn test_lu_backward_error() {
let a = [2.0_f32, 1.0, 1.0, 4.0, 3.0, 3.0, 8.0, 7.0, 9.0];
let b = [4.0_f32, 10.0, 24.0];
let lu = lu_factorize(&a, 3).unwrap();
let x = lu.solve(&b).unwrap();
let ax = dense_matvec(&a, &x, 3, 3);
let residual: Vec<f32> = ax.iter().zip(b.iter()).map(|(a, b)| a - b).collect();
let res_norm = vec_inf_norm(&residual);
let a_norm = mat_inf_norm(&a, 3, 3);
let x_norm = vec_inf_norm(&x);
let backward_error = res_norm / (a_norm * x_norm);
assert!(backward_error < 1e-5, "LU backward error too large: {}", backward_error);
}
#[test]
fn test_lu_singular_detected() {
let a = [1.0_f32, 2.0, 2.0, 4.0];
let result = lu_factorize(&a, 2);
assert!(result.is_err(), "Should detect singular matrix");
}
#[test]
fn test_lu_solution_residual() {
let a = [3.0_f32, 2.0, 1.0, 4.0];
let b = [5.0_f32, 5.0];
let lu = lu_factorize(&a, 2).unwrap();
let x = lu.solve(&b).unwrap();
let ax = dense_matvec(&a, &x, 2, 2);
for i in 0..2 {
assert!(
(ax[i] - b[i]).abs() < 1e-4,
"LU residual: Ax[{}]={}, b[{}]={}",
i,
ax[i],
i,
b[i]
);
}
}
#[test]
fn test_qr_orthogonality() {
let a = [1.0_f32, 2.0, 3.0, 4.0, 5.0, 6.0];
let qr = qr_factorize(&a, 3, 2).unwrap();
let q = qr.extract_q();
let mut qt = vec![0.0_f32; 9];
for i in 0..3 {
for j in 0..3 {
qt[i * 3 + j] = q[j * 3 + i]; }
}
let qtq = dense_matmul(&qt, &q, 3, 3, 3);
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(qtq[i * 3 + j] - expected).abs() < 1e-4,
"Q^T*Q[{},{}] = {}, expected {}",
i,
j,
qtq[i * 3 + j],
expected
);
}
}
}
#[test]
fn test_qr_reconstruction() {
let a = [1.0_f32, 2.0, 3.0, 4.0, 5.0, 6.0]; let qr = qr_factorize(&a, 3, 2).unwrap();
let q = qr.extract_q(); let r = qr.extract_r();
let mut r_full = vec![0.0_f32; 3 * 2];
for i in 0..2 {
for j in 0..2 {
r_full[i * 2 + j] = r[i * 2 + j];
}
}
let qr_product = dense_matmul(&q, &r_full, 3, 3, 2);
for i in 0..6 {
assert!(
(qr_product[i] - a[i]).abs() < 1e-4,
"QR reconstruction: (QR)[{}] = {}, A[{}] = {}",
i,
qr_product[i],
i,
a[i]
);
}
}
#[test]
fn test_svd_singular_values_nonneg_decreasing() {
let a = [3.0_f32, 2.0, 2.0, 2.0, 3.0, -2.0]; let result = svd(&a, 2, 3).unwrap();
for s in &result.sigma {
assert!(*s >= 0.0, "Singular value should be non-negative: {}", s);
}
for i in 1..result.sigma.len() {
assert!(
result.sigma[i - 1] >= result.sigma[i] - 1e-6,
"Singular values not decreasing: s[{}]={} < s[{}]={}",
i - 1,
result.sigma[i - 1],
i,
result.sigma[i]
);
}
}
#[test]
fn test_svd_reconstruction() {
let a = [1.0_f32, 2.0, 3.0, 4.0]; let result = svd(&a, 2, 2).unwrap();
let m = result.m;
let n = result.n;
let min_mn = m.min(n);
let mut u_sigma = vec![0.0_f32; m * n];
for i in 0..m {
for j in 0..min_mn {
u_sigma[i * n + j] = result.u[i * m + j] * result.sigma[j];
}
}
let reconstructed = dense_matmul(&u_sigma, &result.vt, m, n, n);
for i in 0..4 {
assert!(
(reconstructed[i] - a[i]).abs() < 1e-3,
"SVD reconstruction: A_approx[{}]={}, A[{}]={}",
i,
reconstructed[i],
i,
a[i]
);
}
}
#[test]
fn test_svd_orthogonality_u() {
let a = [1.0_f32, 2.0, 3.0, 4.0]; let result = svd(&a, 2, 2).unwrap();
let m = result.m;
let mut ut = vec![0.0_f32; m * m];
for i in 0..m {
for j in 0..m {
ut[i * m + j] = result.u[j * m + i];
}
}
let utu = dense_matmul(&ut, &result.u, m, m, m);
for i in 0..m {
for j in 0..m {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(utu[i * m + j] - expected).abs() < 1e-3,
"U^T*U[{},{}] = {}, expected {}",
i,
j,
utu[i * m + j],
expected
);
}
}
let a_tall = [1.0_f32, 2.0, 3.0, 4.0, 5.0, 6.0]; let result_tall = svd(&a_tall, 3, 2).unwrap();
let m2 = result_tall.m;
let min_mn = m2.min(result_tall.n);
for i in 0..min_mn {
for j in 0..min_mn {
let mut dot = 0.0_f32;
for k in 0..m2 {
dot += result_tall.u[k * m2 + i] * result_tall.u[k * m2 + j];
}
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(dot - expected).abs() < 1e-3,
"U columns ({},{}) dot = {}, expected {}",
i,
j,
dot,
expected
);
}
}
}
#[test]
fn test_svd_singular_values_nonneg() {
let a = [1.0_f32, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 3.0]; let result = svd(&a, 3, 3).unwrap();
for (i, s) in result.sigma.iter().enumerate() {
assert!(*s >= -1e-7, "Singular value {} should be non-negative: {}", i, s);
}
assert!((result.sigma[0] - 3.0).abs() < 1e-4, "s[0]={}", result.sigma[0]);
assert!((result.sigma[1] - 2.0).abs() < 1e-4, "s[1]={}", result.sigma[1]);
assert!((result.sigma[2] - 1.0).abs() < 1e-4, "s[2]={}", result.sigma[2]);
}
#[test]
fn test_syrk_identity() {
let a = [1.0_f32, 0.0, 0.0, 1.0]; let mut c = vec![0.0_f32; 4];
syrk(&a, &mut c, 2, 2, 1.0, 0.0).unwrap();
assert!((c[0] - 1.0).abs() < 1e-5);
assert!((c[1] - 0.0).abs() < 1e-5);
assert!((c[2] - 0.0).abs() < 1e-5);
assert!((c[3] - 1.0).abs() < 1e-5);
assert!(is_symmetric(&c, 2, 1e-6));
}
#[test]
fn test_syrk_known_value() {
let a = [1.0_f32, 2.0, 3.0, 4.0];
let mut c = vec![0.0_f32; 4];
syrk(&a, &mut c, 2, 2, 1.0, 0.0).unwrap();
assert!((c[0] - 5.0).abs() < 1e-5, "C[0,0]={}, expected 5", c[0]);
assert!((c[1] - 11.0).abs() < 1e-5, "C[0,1]={}, expected 11", c[1]);
assert!((c[2] - 11.0).abs() < 1e-5, "C[1,0]={}, expected 11", c[2]);
assert!((c[3] - 25.0).abs() < 1e-5, "C[1,1]={}, expected 25", c[3]);
}
#[test]
fn test_syrk_symmetry() {
let a = [1.0_f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
let mut c = vec![0.0_f32; 9];
syrk(&a, &mut c, 3, 3, 1.0, 0.0).unwrap();
assert!(is_symmetric(&c, 3, 1e-5), "SYRK result must be symmetric: {:?}", c);
}
#[test]
fn test_trmm_identity() {
let a = [1.0_f32, 0.0, 0.0, 1.0]; let mut b = vec![5.0_f32, 6.0, 7.0, 8.0];
trmm(&a, &mut b, 2, 2, 1.0).unwrap();
assert!((b[0] - 5.0).abs() < 1e-5);
assert!((b[1] - 6.0).abs() < 1e-5);
assert!((b[2] - 7.0).abs() < 1e-5);
assert!((b[3] - 8.0).abs() < 1e-5);
}
#[test]
fn test_trmm_lower_triangular() {
let a = [2.0_f32, 0.0, 3.0, 4.0];
let mut b = vec![1.0_f32, 0.0, 0.0, 1.0];
trmm(&a, &mut b, 2, 2, 1.0).unwrap();
assert!((b[0] - 2.0).abs() < 1e-5, "b[0,0]={}", b[0]);
assert!((b[1] - 0.0).abs() < 1e-5, "b[0,1]={}", b[1]);
assert!((b[2] - 3.0).abs() < 1e-5, "b[1,0]={}", b[2]);
assert!((b[3] - 4.0).abs() < 1e-5, "b[1,1]={}", b[3]);
}
#[test]
fn test_symm_known_product() {
let a = [2.0_f32, 1.0, 1.0, 3.0];
let b = [1.0_f32, 0.0, 0.0, 2.0];
let mut c = vec![0.0_f32; 4];
symm(&a, &b, &mut c, 2, 2, 1.0, 0.0).unwrap();
assert!((c[0] - 2.0).abs() < 1e-5, "C[0,0]={}, expected 2", c[0]);
assert!((c[1] - 2.0).abs() < 1e-5, "C[0,1]={}, expected 2", c[1]);
assert!((c[2] - 1.0).abs() < 1e-5, "C[1,0]={}, expected 1", c[2]);
assert!((c[3] - 6.0).abs() < 1e-5, "C[1,1]={}, expected 6", c[3]);
}
#[test]
fn test_trsm_backward_error() {
let a = [2.0_f32, 0.0, 3.0, 4.0];
let b = [4.0_f32, 6.0, 11.0, 16.0];
let n = 2;
let nrhs = 2;
let result = trsm(&a, &b, n, nrhs, TriangularSide::Lower, DiagonalType::NonUnit).unwrap();
let x = &result.x;
for col in 0..nrhs {
for i in 0..n {
let mut ax_ij = 0.0_f32;
for j in 0..n {
ax_ij += a[i * n + j] * x[j * nrhs + col];
}
assert!(
(ax_ij - b[i * nrhs + col]).abs() < 1e-4,
"TRSM backward error: AX[{},{}]={}, B[{},{}]={}",
i,
col,
ax_ij,
i,
col,
b[i * nrhs + col]
);
}
}
}
#[test]
fn test_trsm_singular_detected() {
let a = [1.0_f32, 0.0, 2.0, 0.0];
let b = [1.0_f32, 2.0];
let result = trsm(&a, &b, 2, 1, TriangularSide::Lower, DiagonalType::NonUnit);
assert!(result.is_err(), "Should detect singular triangular matrix");
}
}