Skip to main content

pedalkernel_validate/
npy.rs

1//! NumPy `.npy` file I/O.
2//!
3//! This module provides functions to read and write NumPy `.npy` files,
4//! enabling interoperability with Python-based analysis tools.
5//!
6//! # Example
7//!
8//! ```rust,ignore
9//! use pedalkernel_validate::npy;
10//!
11//! // Write test output
12//! let output = vec![0.1, 0.2, 0.3, 0.4, 0.5];
13//! npy::write_f64("output.npy", &output).unwrap();
14//!
15//! // Read reference data
16//! let reference = npy::read_f64("golden.npy").unwrap();
17//!
18//! // Check if a file exists
19//! if npy::exists("reference.npy") {
20//!     // Load and compare
21//! }
22//! ```
23//!
24//! # Python Interoperability
25//!
26//! Files written by this module can be loaded in Python:
27//!
28//! ```python
29//! import numpy as np
30//! data = np.load("output.npy")
31//! ```
32//!
33//! And Python-generated files can be read:
34//!
35//! ```python
36//! import numpy as np
37//! golden = np.sin(np.linspace(0, 2*np.pi, 1000))
38//! np.save("golden.npy", golden)
39//! ```
40
41use ndarray::Array1;
42use ndarray_npy::{ReadNpyError, WriteNpyError};
43use std::path::Path;
44use thiserror::Error;
45
46#[derive(Error, Debug)]
47pub enum NpyError {
48    #[error("Failed to read NPY file: {0}")]
49    ReadError(#[from] ReadNpyError),
50    #[error("Failed to write NPY file: {0}")]
51    WriteError(#[from] WriteNpyError),
52    #[error("IO error: {0}")]
53    IoError(#[from] std::io::Error),
54}
55
56/// Read a 1D float64 array from an NPY file.
57pub fn read_f64(path: impl AsRef<Path>) -> Result<Vec<f64>, NpyError> {
58    let arr: Array1<f64> = ndarray_npy::read_npy(path)?;
59    Ok(arr.to_vec())
60}
61
62/// Write a 1D float64 array to an NPY file.
63pub fn write_f64(path: impl AsRef<Path>, data: &[f64]) -> Result<(), NpyError> {
64    let path = path.as_ref();
65
66    // Ensure parent directory exists
67    if let Some(parent) = path.parent() {
68        std::fs::create_dir_all(parent)?;
69    }
70
71    let arr = Array1::from_vec(data.to_vec());
72    ndarray_npy::write_npy(path, &arr)?;
73    Ok(())
74}
75
76/// Check if an NPY file exists.
77pub fn exists(path: impl AsRef<Path>) -> bool {
78    path.as_ref().exists()
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use tempfile::NamedTempFile;
85
86    #[test]
87    fn roundtrip_f64_array() {
88        let data: Vec<f64> = (0..100).map(|i| i as f64 * 0.1).collect();
89
90        let tmpfile = NamedTempFile::new().unwrap();
91        let path = tmpfile.path().to_path_buf();
92
93        // Write
94        write_f64(&path, &data).unwrap();
95
96        // Read back
97        let loaded = read_f64(&path).unwrap();
98
99        assert_eq!(data.len(), loaded.len());
100        for (a, b) in data.iter().zip(loaded.iter()) {
101            assert!((a - b).abs() < 1e-10);
102        }
103    }
104}