use sparse_npz::{Compression, CscMatrix, Format, Values};
fn temp_path(tag: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("sparse_npz_test_{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
dir.join(format!("{tag}.npz"))
}
fn sample(values: Values) -> CscMatrix {
CscMatrix {
rows: 3,
cols: 4,
col_ptr: vec![0, 1, 2, 3, 3],
row_indices: vec![0, 2, 1],
values,
}
}
#[test]
fn csc_write_then_read_roundtrips() {
let path = temp_path("csc");
let m = sample(Values::I64(vec![5, 3, 7]));
m.write_npz(&path).unwrap();
let back = CscMatrix::read_npz(&path).unwrap();
assert_eq!(back, m);
let mut got: Vec<(usize, usize, f64)> = back.entries().collect();
got.sort_by_key(|&(r, c, _)| (c, r));
assert_eq!(got, vec![(0, 0, 5.0), (2, 1, 3.0), (1, 2, 7.0)]);
}
#[test]
fn element_dtype_is_preserved_across_roundtrip() {
let cases = [
("bool", Values::Bool(vec![true, true, true])),
("i8", Values::I8(vec![-5, 3, 7])),
("u8", Values::U8(vec![5, 3, 7])),
("i16", Values::I16(vec![-500, 300, 700])),
("u16", Values::U16(vec![500, 300, 700])),
("i32", Values::I32(vec![-5, 3, 7])),
("u32", Values::U32(vec![5, 3, 7])),
("i64", Values::I64(vec![-5, 3, 7])),
("u64", Values::U64(vec![5, 3, 7])),
("f32", Values::F32(vec![1.5, 2.5, -3.5])),
("f64", Values::F64(vec![1.5, 2.5, -3.5])),
];
for (tag, values) in cases {
let path = temp_path(tag);
let m = sample(values);
m.write_npz(&path).unwrap();
let back = CscMatrix::read_npz(&path).unwrap();
assert_eq!(back.values, m.values, "dtype {tag} not preserved");
}
}
#[test]
fn csr_write_reports_csr_and_preserves_entries() {
let path = temp_path("csr");
let m = sample(Values::I64(vec![5, 3, 7]));
m.write_npz_as(&path, Format::Csr).unwrap();
let (back, format) = CscMatrix::read_npz_with_format(&path).unwrap();
assert_eq!(format, Format::Csr);
assert_eq!(back, m);
}
#[test]
fn csc_write_reports_csc() {
let path = temp_path("csc_fmt");
let m = sample(Values::I64(vec![5, 3, 7]));
m.write_npz(&path).unwrap();
let (_, format) = CscMatrix::read_npz_with_format(&path).unwrap();
assert_eq!(format, Format::Csc);
}
#[test]
fn zstd_compressed_roundtrips_and_preserves_dtype() {
let path = temp_path("zstd");
let m = sample(Values::U32(vec![5, 3, 7]));
m.write_npz_with(&path, Format::Csc, Compression::Zstd { level: None }).unwrap();
let back = CscMatrix::read_npz(&path).unwrap();
assert_eq!(back, m);
}
#[test]
fn zstd_levels_roundtrip() {
let m = sample(Values::I64(vec![5, 3, 7]));
for level in [Some(-5), Some(1), Some(3), Some(19), Some(22), None] {
let path = temp_path(&format!("zstd_lvl_{level:?}"));
m.write_npz_with(&path, Format::Csc, Compression::Zstd { level }).unwrap();
assert_eq!(CscMatrix::read_npz(&path).unwrap(), m);
}
}
#[test]
fn deflate_levels_roundtrip() {
let m = sample(Values::I64(vec![5, 3, 7]));
for level in [Some(1), Some(6), Some(9), Some(10), Some(264), None] {
let path = temp_path(&format!("deflate_lvl_{level:?}"));
m.write_npz_with(&path, Format::Csc, Compression::Deflate { level }).unwrap();
assert_eq!(CscMatrix::read_npz(&path).unwrap(), m);
}
}
#[test]
fn zstd_and_deflate_read_identically() {
let m = sample(Values::I64(vec![5, 3, 7]));
let deflate = temp_path("cmp_deflate");
let zstd = temp_path("cmp_zstd");
m.write_npz_with(&deflate, Format::Csr, Compression::Deflate { level: Some(9) }).unwrap();
m.write_npz_with(&zstd, Format::Csr, Compression::Zstd { level: Some(19) }).unwrap();
let (a, fa) = CscMatrix::read_npz_with_format(&deflate).unwrap();
let (b, fb) = CscMatrix::read_npz_with_format(&zstd).unwrap();
assert_eq!(a, b);
assert_eq!(fa, Format::Csr);
assert_eq!(fb, Format::Csr);
}
#[test]
fn nnz_and_entries_agree() {
let m = sample(Values::I64(vec![5, 3, 7]));
assert_eq!(m.nnz(), 3);
assert_eq!(m.entries().count(), 3);
}