use ndarray::{Array1, Array2, ArrayView1};
use ndarray_npy::NpzWriter;
use crate::arrays::CooMatrix;
use crate::error::{Error, Result};
fn wrap(path: &str, err: impl std::fmt::Display) -> Error {
Error::io(path, std::io::Error::other(err.to_string()))
}
fn writer(path: &str) -> Result<NpzWriter<std::fs::File>> {
let file = std::fs::File::create(path).map_err(|e| Error::io(path, e))?;
Ok(NpzWriter::new(file))
}
pub fn write_dense(path: &str, values: &Array2<f32>) -> Result<()> {
let mut npz = writer(path)?;
npz.add_array("values", values).map_err(|e| wrap(path, e))?;
npz.finish().map_err(|e| wrap(path, e))?;
Ok(())
}
pub fn write_sparse(path: &str, coo: &CooMatrix) -> Result<()> {
let mut npz = writer(path)?;
npz.add_array("values", &ArrayView1::from(&coo.values[..]))
.map_err(|e| wrap(path, e))?;
npz.add_array("row", &ArrayView1::from(&coo.row[..]))
.map_err(|e| wrap(path, e))?;
npz.add_array("col", &ArrayView1::from(&coo.col[..]))
.map_err(|e| wrap(path, e))?;
npz.add_array(
"shape",
&Array1::from(vec![coo.shape.0 as u32, coo.shape.1 as u32]),
)
.map_err(|e| wrap(path, e))?;
npz.finish().map_err(|e| wrap(path, e))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn entry_names(bytes: &[u8]) -> Vec<String> {
let mut names = Vec::new();
let mut i = 0;
while i + 30 <= bytes.len() {
if bytes[i..i + 4] != [0x50, 0x4b, 0x03, 0x04] {
i += 1;
continue;
}
let name_len = u16::from_le_bytes([bytes[i + 26], bytes[i + 27]]) as usize;
names.push(String::from_utf8_lossy(&bytes[i + 30..i + 30 + name_len]).into_owned());
i += 30 + name_len;
}
names
}
#[test]
fn a_dense_save_writes_one_entry_named_values() {
let dir = std::env::temp_dir().join("gwseq_npz_dense");
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("out.npz");
let values = Array2::from_shape_vec((2, 3), (0..6).map(|v| v as f32).collect()).unwrap();
write_dense(path.to_str().unwrap(), &values).unwrap();
let bytes = std::fs::read(&path).unwrap();
assert_eq!(entry_names(&bytes), ["values.npy"]);
assert!(
bytes.windows(6).any(|w| w == b"\x93NUMPY"),
"no .npy magic in the entry"
);
let text = String::from_utf8_lossy(&bytes[..200]);
assert!(text.contains("'shape': (2, 3)"), "{text:?}");
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn a_sparse_save_writes_the_four_documented_keys() {
let dir = std::env::temp_dir().join("gwseq_npz_sparse");
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("out.npz");
let coo = CooMatrix {
values: vec![1.0, 2.0],
row: vec![0, 1],
col: vec![1, 0],
shape: (2, 2),
};
write_sparse(path.to_str().unwrap(), &coo).unwrap();
let bytes = std::fs::read(&path).unwrap();
assert_eq!(
entry_names(&bytes),
["values.npy", "row.npy", "col.npy", "shape.npy"]
);
std::fs::remove_dir_all(&dir).ok();
}
}