#[cfg(test)]
mod sparse_tests {
use crate::tensor::Tensor;
fn f32_tensor(data: Vec<f32>, shape: Vec<usize>) -> Tensor {
match Tensor::with_shape(data, shape) {
Ok(t) => t,
Err(e) => panic!("failed to build f32 tensor: {}", e),
}
}
fn to_vec(t: &Tensor) -> Vec<f32> {
match t.to_vec_f32() {
Ok(v) => v,
Err(e) => panic!("to_vec_f32 failed: {}", e),
}
}
#[test]
fn test_dense_tensor_is_not_sparse() {
let t = f32_tensor(vec![1.0, 0.0, 0.0, 2.0], vec![2, 2]);
assert!(!t.is_sparse(), "a freshly built F32 tensor must not be sparse");
}
#[test]
fn test_to_sparse_produces_sparse_tensor() {
let t = f32_tensor(vec![1.0, 0.0, 0.0, 0.0, 2.0, 0.0], vec![2, 3]);
match t.to_sparse(0.5) {
Ok(sparse) => assert!(sparse.is_sparse(), "result of to_sparse must be sparse"),
Err(e) => panic!("to_sparse failed: {}", e),
}
}
#[test]
fn test_already_sparse_stays_sparse() {
let t = f32_tensor(vec![0.0, 1.0, 0.0, 0.0], vec![2, 2]);
match t.to_sparse(0.5) {
Ok(sparse) => match sparse.to_sparse(0.5) {
Ok(again) => assert!(again.is_sparse()),
Err(e) => panic!("second to_sparse failed: {}", e),
},
Err(e) => panic!("first to_sparse failed: {}", e),
}
}
#[test]
fn test_to_sparse_non_f32_returns_err() {
let t = match Tensor::from_vec_i64(vec![1, 2, 3, 4], &[4]) {
Ok(t) => t,
Err(e) => panic!("build i64 tensor failed: {}", e),
};
assert!(
t.to_sparse(0.5).is_err(),
"i64 tensor → to_sparse should return Err"
);
}
#[test]
fn test_dense_to_dense_is_identity() {
let data = vec![1.0, 2.0, 3.0, 4.0];
let t = f32_tensor(data.clone(), vec![2, 2]);
match t.to_dense() {
Ok(dense) => {
let v = to_vec(&dense);
assert_eq!(v, data, "dense→dense must preserve values");
},
Err(e) => panic!("to_dense on dense failed: {}", e),
}
}
#[test]
fn test_sparse_to_dense_roundtrip() {
let data = vec![1.0, 0.0, 0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 9.0];
let t = f32_tensor(data.clone(), vec![3, 3]);
match t.to_sparse(0.5) {
Ok(sparse) => match sparse.to_dense() {
Ok(dense) => {
let v = to_vec(&dense);
assert_eq!(v.len(), 9);
assert!((v[0] - 1.0).abs() < 1e-6, "pos 0 must be 1.0, got {}", v[0]);
assert!((v[4] - 5.0).abs() < 1e-6, "pos 4 must be 5.0, got {}", v[4]);
assert!((v[8] - 9.0).abs() < 1e-6, "pos 8 must be 9.0, got {}", v[8]);
},
Err(e) => panic!("to_dense after sparse failed: {}", e),
},
Err(e) => panic!("to_sparse in roundtrip failed: {}", e),
}
}
#[test]
fn test_all_zeros_sparse_to_dense() {
let t = f32_tensor(vec![0.0; 6], vec![2, 3]);
match t.to_sparse(0.5) {
Ok(sparse) => match sparse.to_dense() {
Ok(dense) => {
let v = to_vec(&dense);
assert!(v.iter().all(|&x| x == 0.0), "all-zeros roundtrip must stay zero");
},
Err(e) => panic!("to_dense all-zeros failed: {}", e),
},
Err(e) => panic!("to_sparse all-zeros failed: {}", e),
}
}
#[test]
fn test_sparsity_all_zeros() {
let t = f32_tensor(vec![0.0; 8], vec![2, 4]);
match t.sparsity() {
Ok(s) => assert!((s - 1.0).abs() < 1e-6, "all-zeros → sparsity=1.0, got {}", s),
Err(e) => panic!("sparsity all-zeros failed: {}", e),
}
}
#[test]
fn test_sparsity_no_zeros() {
let t = f32_tensor(vec![1.0; 8], vec![2, 4]);
match t.sparsity() {
Ok(s) => assert!((s - 0.0).abs() < 1e-6, "no-zeros → sparsity=0.0, got {}", s),
Err(e) => panic!("sparsity no-zeros failed: {}", e),
}
}
#[test]
fn test_sparsity_half() {
let t = f32_tensor(vec![1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0], vec![2, 4]);
match t.sparsity() {
Ok(s) => assert!((s - 0.5).abs() < 1e-6, "half-zeros → sparsity=0.5, got {}", s),
Err(e) => panic!("sparsity half failed: {}", e),
}
}
#[test]
fn test_sparsity_of_sparse_tensor() {
let t = f32_tensor(vec![1.0, 0.0, 0.0, 0.0], vec![2, 2]);
match t.to_sparse(0.5) {
Ok(sparse) => match sparse.sparsity() {
Ok(s) => {
assert!(s >= 0.5, "sparse tensor sparsity must be ≥ 0.5, got {}", s);
},
Err(e) => panic!("sparsity on sparse tensor failed: {}", e),
},
Err(e) => panic!("to_sparse in sparsity test failed: {}", e),
}
}
#[test]
fn test_nnz_dense_all_nonzero() {
let t = f32_tensor(vec![1.0; 6], vec![2, 3]);
match t.nnz() {
Ok(n) => assert_eq!(n, 6, "all-nonzero → nnz=6"),
Err(e) => panic!("nnz all-nonzero failed: {}", e),
}
}
#[test]
fn test_nnz_dense_mixed() {
let t = f32_tensor(vec![0.0, 1.0, 0.0, 2.0, 0.0, 3.0], vec![2, 3]);
match t.nnz() {
Ok(n) => assert_eq!(n, 3, "3 non-zeros expected, got {}", n),
Err(e) => panic!("nnz mixed failed: {}", e),
}
}
#[test]
fn test_nnz_sparse_tensor() {
match Tensor::sparse_coo(
vec![vec![0, 1], vec![1, 2]],
vec![7.0, 8.0],
vec![3, 4],
) {
Ok(sparse) => match sparse.nnz() {
Ok(n) => assert_eq!(n, 2, "COO with 2 values → nnz=2, got {}", n),
Err(e) => panic!("nnz on sparse_coo failed: {}", e),
},
Err(e) => panic!("sparse_coo construction failed: {}", e),
}
}
#[test]
fn test_sparse_coo_construction() {
match Tensor::sparse_coo(
vec![vec![0, 1, 2], vec![0, 1, 2]],
vec![1.0, 2.0, 3.0],
vec![3, 3],
) {
Ok(t) => {
assert!(t.is_sparse());
match t.nnz() {
Ok(n) => assert_eq!(n, 3),
Err(e) => panic!("nnz on COO failed: {}", e),
}
},
Err(e) => panic!("sparse_coo diagonal failed: {}", e),
}
}
#[test]
fn test_sparse_coo_single_element() {
match Tensor::sparse_coo(vec![vec![0], vec![0]], vec![42.0], vec![5, 5]) {
Ok(t) => {
assert!(t.is_sparse());
match t.nnz() {
Ok(n) => assert_eq!(n, 1),
Err(e) => panic!("nnz single elem COO: {}", e),
}
},
Err(e) => panic!("sparse_coo single elem failed: {}", e),
}
}
#[test]
fn test_sparse_coo_to_dense_values() {
match Tensor::sparse_coo(vec![vec![0], vec![1]], vec![5.0], vec![2, 2]) {
Ok(sparse) => match sparse.to_dense() {
Ok(dense) => {
let v = to_vec(&dense);
assert_eq!(v.len(), 4);
assert!((v[1] - 5.0).abs() < 1e-6, "pos (0,1) must be 5.0, got {}", v[1]);
assert!(v[0].abs() < 1e-6, "pos (0,0) must be 0, got {}", v[0]);
assert!(v[2].abs() < 1e-6, "pos (1,0) must be 0, got {}", v[2]);
assert!(v[3].abs() < 1e-6, "pos (1,1) must be 0, got {}", v[3]);
},
Err(e) => panic!("to_dense from COO failed: {}", e),
},
Err(e) => panic!("sparse_coo 2×2 single failed: {}", e),
}
}
#[test]
fn test_sparse_csr_construction() {
match Tensor::sparse_csr(
vec![0, 1, 2, 3],
vec![0, 1, 2],
vec![1.0, 1.0, 1.0],
vec![3, 3],
) {
Ok(t) => {
assert!(t.is_sparse());
match t.nnz() {
Ok(n) => assert_eq!(n, 3, "identity CSR → nnz=3, got {}", n),
Err(e) => panic!("nnz on CSR failed: {}", e),
}
},
Err(e) => panic!("sparse_csr identity failed: {}", e),
}
}
#[test]
fn test_sparse_csr_to_dense_roundtrip() {
match Tensor::sparse_csr(vec![0, 1, 2], vec![1, 3], vec![3.0, 7.0], vec![2, 4]) {
Ok(sparse) => match sparse.to_dense() {
Ok(dense) => {
let v = to_vec(&dense);
assert_eq!(v.len(), 8);
assert!((v[1] - 3.0).abs() < 1e-6, "pos (0,1) must be 3.0, got {}", v[1]);
assert!((v[7] - 7.0).abs() < 1e-6, "pos (1,3) must be 7.0, got {}", v[7]);
},
Err(e) => panic!("to_dense CSR failed: {}", e),
},
Err(e) => panic!("sparse_csr 2×4 failed: {}", e),
}
}
#[test]
fn test_sparse_csr_empty_row() {
match Tensor::sparse_csr(vec![0, 0, 1, 1], vec![2], vec![9.0], vec![3, 3]) {
Ok(t) => match t.nnz() {
Ok(n) => assert_eq!(n, 1, "single element CSR → nnz=1"),
Err(e) => panic!("nnz CSR empty row failed: {}", e),
},
Err(e) => panic!("sparse_csr empty row failed: {}", e),
}
}
#[test]
fn test_to_sparse_threshold_keeps_large_values() {
let t = f32_tensor(vec![0.5, 1.0, 0.3, 0.0], vec![2, 2]);
match t.to_sparse(0.9) {
Ok(sparse) => match sparse.nnz() {
Ok(n) => assert_eq!(n, 1, "only value ≥ 0.9 → nnz=1, got {}", n),
Err(e) => panic!("nnz after threshold failed: {}", e),
},
Err(e) => panic!("to_sparse with threshold 0.9 failed: {}", e),
}
}
#[test]
fn test_to_sparse_zero_threshold_keeps_all_nonzeros() {
let data = vec![0.0, 1.0, 2.0, 0.0, 3.0, 0.0];
let t = f32_tensor(data, vec![2, 3]);
match t.to_sparse(0.0) {
Ok(sparse) => match sparse.nnz() {
Ok(n) => assert_eq!(n, 3, "3 nonzeros with threshold=0, got {}", n),
Err(e) => panic!("nnz zero-threshold failed: {}", e),
},
Err(e) => panic!("to_sparse zero-threshold failed: {}", e),
}
}
}