1use ndarray::{Array1, Array2, ArrayView1};
10use ndarray_npy::NpzWriter;
11
12use crate::arrays::CooMatrix;
13use crate::error::{Error, Result};
14
15fn wrap(path: &str, err: impl std::fmt::Display) -> Error {
18 Error::io(path, std::io::Error::other(err.to_string()))
19}
20
21fn writer(path: &str) -> Result<NpzWriter<std::fs::File>> {
22 let file = std::fs::File::create(path).map_err(|e| Error::io(path, e))?;
23 Ok(NpzWriter::new(file))
24}
25
26pub fn write_dense(path: &str, values: &Array2<f32>) -> Result<()> {
28 let mut npz = writer(path)?;
29 npz.add_array("values", values).map_err(|e| wrap(path, e))?;
30 npz.finish().map_err(|e| wrap(path, e))?;
31 Ok(())
32}
33
34pub fn write_sparse(path: &str, coo: &CooMatrix) -> Result<()> {
40 let mut npz = writer(path)?;
41 npz.add_array("values", &ArrayView1::from(&coo.values[..]))
45 .map_err(|e| wrap(path, e))?;
46 npz.add_array("row", &ArrayView1::from(&coo.row[..]))
47 .map_err(|e| wrap(path, e))?;
48 npz.add_array("col", &ArrayView1::from(&coo.col[..]))
49 .map_err(|e| wrap(path, e))?;
50 npz.add_array(
51 "shape",
52 &Array1::from(vec![coo.shape.0 as u32, coo.shape.1 as u32]),
53 )
54 .map_err(|e| wrap(path, e))?;
55 npz.finish().map_err(|e| wrap(path, e))?;
56 Ok(())
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 fn entry_names(bytes: &[u8]) -> Vec<String> {
65 let mut names = Vec::new();
66 let mut i = 0;
67 while i + 30 <= bytes.len() {
68 if bytes[i..i + 4] != [0x50, 0x4b, 0x03, 0x04] {
69 i += 1;
70 continue;
71 }
72 let name_len = u16::from_le_bytes([bytes[i + 26], bytes[i + 27]]) as usize;
73 names.push(String::from_utf8_lossy(&bytes[i + 30..i + 30 + name_len]).into_owned());
74 i += 30 + name_len;
75 }
76 names
77 }
78
79 #[test]
80 fn a_dense_save_writes_one_entry_named_values() {
81 let dir = std::env::temp_dir().join("gwseq_npz_dense");
82 std::fs::create_dir_all(&dir).unwrap();
83 let path = dir.join("out.npz");
84 let values = Array2::from_shape_vec((2, 3), (0..6).map(|v| v as f32).collect()).unwrap();
85 write_dense(path.to_str().unwrap(), &values).unwrap();
86
87 let bytes = std::fs::read(&path).unwrap();
88 assert_eq!(entry_names(&bytes), ["values.npy"]);
89 assert!(
93 bytes.windows(6).any(|w| w == b"\x93NUMPY"),
94 "no .npy magic in the entry"
95 );
96 let text = String::from_utf8_lossy(&bytes[..200]);
97 assert!(text.contains("'shape': (2, 3)"), "{text:?}");
98 std::fs::remove_dir_all(&dir).ok();
99 }
100
101 #[test]
102 fn a_sparse_save_writes_the_four_documented_keys() {
103 let dir = std::env::temp_dir().join("gwseq_npz_sparse");
104 std::fs::create_dir_all(&dir).unwrap();
105 let path = dir.join("out.npz");
106 let coo = CooMatrix {
107 values: vec![1.0, 2.0],
108 row: vec![0, 1],
109 col: vec![1, 0],
110 shape: (2, 2),
111 };
112 write_sparse(path.to_str().unwrap(), &coo).unwrap();
113
114 let bytes = std::fs::read(&path).unwrap();
115 assert_eq!(
116 entry_names(&bytes),
117 ["values.npy", "row.npy", "col.npy", "shape.npy"]
118 );
119 std::fs::remove_dir_all(&dir).ok();
120 }
121}