Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
325
crates/ruvector-solver/tests/test_csr_matrix.rs
Normal file
325
crates/ruvector-solver/tests/test_csr_matrix.rs
Normal file
@@ -0,0 +1,325 @@
|
||||
//! Integration tests for `CsrMatrix` — construction, SpMV, transpose, and
|
||||
//! structural queries.
|
||||
|
||||
mod helpers;
|
||||
|
||||
use approx::assert_relative_eq;
|
||||
use ruvector_solver::types::CsrMatrix;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Construction from COO triplets
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_csr_from_triplets() {
|
||||
// 3x3 matrix:
|
||||
// [ 2 -1 0 ]
|
||||
// [-1 3 -1 ]
|
||||
// [ 0 -1 2 ]
|
||||
let triplets: Vec<(usize, usize, f64)> = vec![
|
||||
(0, 0, 2.0),
|
||||
(0, 1, -1.0),
|
||||
(1, 0, -1.0),
|
||||
(1, 1, 3.0),
|
||||
(1, 2, -1.0),
|
||||
(2, 1, -1.0),
|
||||
(2, 2, 2.0),
|
||||
];
|
||||
|
||||
let mat = CsrMatrix::<f64>::from_coo(3, 3, triplets);
|
||||
|
||||
assert_eq!(mat.rows, 3);
|
||||
assert_eq!(mat.cols, 3);
|
||||
assert_eq!(mat.values.len(), 7);
|
||||
assert_eq!(mat.col_indices.len(), 7);
|
||||
assert_eq!(mat.row_ptr.len(), 4); // rows + 1
|
||||
|
||||
// Verify row_ptr encodes correct row boundaries.
|
||||
assert_eq!(mat.row_ptr[0], 0);
|
||||
assert_eq!(mat.row_ptr[1], 2); // row 0 has 2 entries
|
||||
assert_eq!(mat.row_ptr[2], 5); // row 1 has 3 entries
|
||||
assert_eq!(mat.row_ptr[3], 7); // row 2 has 2 entries
|
||||
|
||||
// Verify row_degree for each row.
|
||||
assert_eq!(mat.row_degree(0), 2);
|
||||
assert_eq!(mat.row_degree(1), 3);
|
||||
assert_eq!(mat.row_degree(2), 2);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SpMV correctness vs dense multiply
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_csr_spmv() {
|
||||
// A = [ 2 -1 0 ] x = [1] Ax = [ 2 - 1 + 0 ] = [ 1 ]
|
||||
// [-1 3 -1 ] [1] [-1 + 3 - 1 ] = [ 1 ]
|
||||
// [ 0 -1 2 ] [1] [ 0 - 1 + 2 ] = [ 1 ]
|
||||
let mat = CsrMatrix::<f64>::from_coo(
|
||||
3,
|
||||
3,
|
||||
vec![
|
||||
(0, 0, 2.0),
|
||||
(0, 1, -1.0),
|
||||
(1, 0, -1.0),
|
||||
(1, 1, 3.0),
|
||||
(1, 2, -1.0),
|
||||
(2, 1, -1.0),
|
||||
(2, 2, 2.0),
|
||||
],
|
||||
);
|
||||
|
||||
let x = vec![1.0, 1.0, 1.0];
|
||||
let mut y = vec![0.0f64; 3];
|
||||
mat.spmv(&x, &mut y);
|
||||
|
||||
assert_relative_eq!(y[0], 1.0, epsilon = 1e-12);
|
||||
assert_relative_eq!(y[1], 1.0, epsilon = 1e-12);
|
||||
assert_relative_eq!(y[2], 1.0, epsilon = 1e-12);
|
||||
|
||||
// Non-trivial x.
|
||||
let x2 = vec![1.0, 2.0, 3.0];
|
||||
let mut y2 = vec![0.0f64; 3];
|
||||
mat.spmv(&x2, &mut y2);
|
||||
|
||||
// Manual: A*[1,2,3] = [2*1 + (-1)*2, -1*1 + 3*2 + (-1)*3, (-1)*2 + 2*3]
|
||||
// = [0, 2, 4]
|
||||
assert_relative_eq!(y2[0], 0.0, epsilon = 1e-12);
|
||||
assert_relative_eq!(y2[1], 2.0, epsilon = 1e-12);
|
||||
assert_relative_eq!(y2[2], 4.0, epsilon = 1e-12);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Density calculation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_csr_density() {
|
||||
// 4x4 matrix with 6 non-zero entries
|
||||
let mat = CsrMatrix::<f64>::from_coo(
|
||||
4,
|
||||
4,
|
||||
vec![
|
||||
(0, 0, 1.0),
|
||||
(1, 1, 2.0),
|
||||
(2, 2, 3.0),
|
||||
(3, 3, 4.0),
|
||||
(0, 1, 0.5),
|
||||
(1, 0, 0.5),
|
||||
],
|
||||
);
|
||||
|
||||
let nnz = mat.values.len();
|
||||
let total_entries = mat.rows * mat.cols;
|
||||
let density = nnz as f64 / total_entries as f64;
|
||||
|
||||
assert_eq!(nnz, 6);
|
||||
assert_relative_eq!(density, 6.0 / 16.0, epsilon = 1e-12);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Transpose correctness
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_csr_transpose() {
|
||||
// Non-symmetric matrix:
|
||||
// A = [ 1 2 0 ] A^T = [ 1 0 3 ]
|
||||
// [ 0 3 0 ] [ 2 3 0 ]
|
||||
// [ 3 0 4 ] [ 0 0 4 ]
|
||||
let mat = CsrMatrix::<f64>::from_coo(
|
||||
3,
|
||||
3,
|
||||
vec![
|
||||
(0, 0, 1.0),
|
||||
(0, 1, 2.0),
|
||||
(1, 1, 3.0),
|
||||
(2, 0, 3.0),
|
||||
(2, 2, 4.0),
|
||||
],
|
||||
);
|
||||
|
||||
let at = mat.transpose();
|
||||
|
||||
assert_eq!(at.rows, 3);
|
||||
assert_eq!(at.cols, 3);
|
||||
assert_eq!(at.values.len(), 5);
|
||||
|
||||
// Verify A^T * e_0. Since A^T[i][j] = A[j][i], the first column of A^T
|
||||
// is the first row of A: [1, 2, 0].
|
||||
let e0 = vec![1.0, 0.0, 0.0];
|
||||
let mut y = vec![0.0f64; 3];
|
||||
at.spmv(&e0, &mut y);
|
||||
// (A^T * e_0)[i] = A^T[i][0] = A[0][i], so [A[0][0], A[0][1], A[0][2]] = [1, 2, 0]
|
||||
assert_relative_eq!(y[0], 1.0, epsilon = 1e-12);
|
||||
assert_relative_eq!(y[1], 2.0, epsilon = 1e-12);
|
||||
assert_relative_eq!(y[2], 0.0, epsilon = 1e-12);
|
||||
|
||||
// Verify transpose of transpose recovers the original.
|
||||
let att = at.transpose();
|
||||
let x = vec![1.0, 2.0, 3.0];
|
||||
let mut y_orig = vec![0.0f64; 3];
|
||||
let mut y_double = vec![0.0f64; 3];
|
||||
mat.spmv(&x, &mut y_orig);
|
||||
att.spmv(&x, &mut y_double);
|
||||
for i in 0..3 {
|
||||
assert_relative_eq!(y_orig[i], y_double[i], epsilon = 1e-12);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Empty matrix handling
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_csr_empty() {
|
||||
let mat = CsrMatrix::<f64>::from_coo(0, 0, Vec::<(usize, usize, f64)>::new());
|
||||
|
||||
assert_eq!(mat.rows, 0);
|
||||
assert_eq!(mat.cols, 0);
|
||||
assert_eq!(mat.values.len(), 0);
|
||||
assert_eq!(mat.row_ptr.len(), 1); // [0]
|
||||
assert_eq!(mat.row_ptr[0], 0);
|
||||
|
||||
// SpMV on empty should work trivially (no-op).
|
||||
let x: Vec<f64> = vec![];
|
||||
let mut y: Vec<f64> = vec![];
|
||||
mat.spmv(&x, &mut y);
|
||||
|
||||
// Transpose of empty is empty.
|
||||
let at = mat.transpose();
|
||||
assert_eq!(at.rows, 0);
|
||||
assert_eq!(at.cols, 0);
|
||||
|
||||
// Matrix with rows but no entries.
|
||||
let mat2 = CsrMatrix::<f64>::from_coo(3, 3, Vec::<(usize, usize, f64)>::new());
|
||||
assert_eq!(mat2.values.len(), 0);
|
||||
let mut y2 = vec![0.0f64; 3];
|
||||
mat2.spmv(&[1.0, 2.0, 3.0], &mut y2);
|
||||
for &v in &y2 {
|
||||
assert_relative_eq!(v, 0.0, epsilon = 1e-15);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Identity matrix: SpMV(I, x) = x
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_csr_identity() {
|
||||
for n in [1, 5, 20, 100] {
|
||||
let identity = CsrMatrix::<f64>::identity(n);
|
||||
|
||||
assert_eq!(identity.rows, n);
|
||||
assert_eq!(identity.cols, n);
|
||||
assert_eq!(identity.values.len(), n);
|
||||
|
||||
// Generate a deterministic test vector.
|
||||
let x: Vec<f64> = (0..n).map(|i| (i as f64 + 1.0) * 0.7).collect();
|
||||
let mut y = vec![0.0f64; n];
|
||||
identity.spmv(&x, &mut y);
|
||||
|
||||
for i in 0..n {
|
||||
assert_relative_eq!(y[i], x[i], epsilon = 1e-12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Diagonal matrix correctness
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_csr_diagonal() {
|
||||
let diag_vals = vec![2.0, 3.0, 5.0, 7.0];
|
||||
let n = diag_vals.len();
|
||||
|
||||
let entries: Vec<(usize, usize, f64)> = diag_vals
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &v)| (i, i, v))
|
||||
.collect();
|
||||
let mat = CsrMatrix::<f64>::from_coo(n, n, entries);
|
||||
|
||||
// D * x = element-wise product of diag and x.
|
||||
let x = vec![1.0, 2.0, 3.0, 4.0];
|
||||
let mut y = vec![0.0f64; n];
|
||||
mat.spmv(&x, &mut y);
|
||||
|
||||
for i in 0..n {
|
||||
assert_relative_eq!(y[i], diag_vals[i] * x[i], epsilon = 1e-12);
|
||||
}
|
||||
|
||||
// Verify each row has exactly 1 non-zero.
|
||||
for i in 0..n {
|
||||
assert_eq!(mat.row_degree(i), 1);
|
||||
}
|
||||
|
||||
// Transpose of diagonal is the same matrix.
|
||||
let dt = mat.transpose();
|
||||
let mut yt = vec![0.0f64; n];
|
||||
dt.spmv(&x, &mut yt);
|
||||
for i in 0..n {
|
||||
assert_relative_eq!(y[i], yt[i], epsilon = 1e-12);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Symmetric detection (structural)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_csr_symmetric() {
|
||||
// Symmetric matrix.
|
||||
let sym = CsrMatrix::<f64>::from_coo(
|
||||
3,
|
||||
3,
|
||||
vec![
|
||||
(0, 0, 2.0),
|
||||
(0, 1, 1.0),
|
||||
(1, 0, 1.0),
|
||||
(1, 1, 3.0),
|
||||
(1, 2, 1.0),
|
||||
(2, 1, 1.0),
|
||||
(2, 2, 2.0),
|
||||
],
|
||||
);
|
||||
|
||||
// Check symmetry by comparing A and A^T via SpMV.
|
||||
let at = sym.transpose();
|
||||
let x = vec![1.0, 2.0, 3.0];
|
||||
let mut y_a = vec![0.0f64; 3];
|
||||
let mut y_at = vec![0.0f64; 3];
|
||||
sym.spmv(&x, &mut y_a);
|
||||
at.spmv(&x, &mut y_at);
|
||||
|
||||
for i in 0..3 {
|
||||
assert_relative_eq!(y_a[i], y_at[i], epsilon = 1e-12);
|
||||
}
|
||||
|
||||
// Use the orchestrator's sparsity analysis to check symmetry detection.
|
||||
let profile = ruvector_solver::router::SolverOrchestrator::analyze_sparsity(&sym);
|
||||
assert!(
|
||||
profile.is_symmetric_structure,
|
||||
"symmetric matrix should be detected as symmetric"
|
||||
);
|
||||
|
||||
// Non-symmetric matrix.
|
||||
let asym = CsrMatrix::<f64>::from_coo(
|
||||
3,
|
||||
3,
|
||||
vec![
|
||||
(0, 0, 1.0),
|
||||
(0, 1, 2.0),
|
||||
// no (1, 0) entry
|
||||
(1, 1, 3.0),
|
||||
(2, 2, 4.0),
|
||||
],
|
||||
);
|
||||
|
||||
let profile_asym = ruvector_solver::router::SolverOrchestrator::analyze_sparsity(&asym);
|
||||
assert!(
|
||||
!profile_asym.is_symmetric_structure,
|
||||
"asymmetric matrix should not be detected as symmetric"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user