use crate::dtype::{ArraysD, DType};
use ndarray::{ArrayD, IxDyn};
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Read, Write};
use std::path::Path;
pub fn savetxt(
path: &Path,
a: &ArraysD,
delimiter: &str,
header: Option<&str>,
comments: &str,
fmt: &str,
) -> std::io::Result<()> {
let mut w = BufWriter::new(File::create(path)?);
if let Some(h) = header {
for line in h.split('\n') {
writeln!(w, "{comments}{line}")?;
}
}
let f = match a.cast(DType::F64) {
ArraysD::F64(x) => x,
_ => return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"savetxt: cast to F64 failed",
)),
};
match f.ndim() {
0 => {
writeln!(w, "{}", fmt_value(f[IxDyn(&[])], fmt))?;
}
1 => {
for &v in f.iter() {
writeln!(w, "{}", fmt_value(v, fmt))?;
}
}
2 => {
let m = f.shape()[0];
let n = f.shape()[1];
for i in 0..m {
let row: Vec<String> =
(0..n).map(|j| fmt_value(f[IxDyn(&[i, j])], fmt)).collect();
writeln!(w, "{}", row.join(delimiter))?;
}
}
_ => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"savetxt: only 0/1/2-D arrays supported",
));
}
}
w.flush()
}
fn fmt_value(v: f64, fmt: &str) -> String {
let s = fmt;
if !s.starts_with('%') {
return format!("{v}");
}
let body = &s[1..];
let mut prec: Option<usize> = None;
let body = if let Some(rest) = body.strip_prefix('.') {
let dig_end = rest.find(|c: char| !c.is_ascii_digit()).unwrap_or(rest.len());
prec = rest[..dig_end].parse().ok();
&rest[dig_end..]
} else {
body
};
match body {
"e" => format!("{v:.*e}", prec.unwrap_or(6)),
"E" => format!("{v:.*E}", prec.unwrap_or(6)),
"f" => format!("{v:.*}", prec.unwrap_or(6)),
"g" | "G" => match prec {
Some(p) => {
let s1 = format!("{v:.*e}", p);
let s2 = format!("{v:.*}", p);
if s2.len() <= s1.len() { s2 } else { s1 }
}
None => format!("{v}"),
},
"d" | "i" => format!("{}", v.round() as i64),
_ => format!("{v}"),
}
}
pub fn loadtxt(
path: &Path,
delimiter: Option<&str>,
comments: &str,
skiprows: usize,
) -> std::io::Result<ArraysD> {
let f = File::open(path)?;
let r = BufReader::new(f);
let mut rows: Vec<Vec<f64>> = Vec::new();
for (i, line) in r.lines().enumerate() {
let line = line?;
if i < skiprows {
continue;
}
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with(comments) {
continue;
}
let parts: Vec<&str> = match delimiter {
Some(d) => trimmed.split(d).collect(),
None => trimmed.split_ascii_whitespace().collect(),
};
let mut row = Vec::with_capacity(parts.len());
for p in parts {
let p = p.trim();
if p.is_empty() {
continue;
}
let v: f64 = p.parse().map_err(|e: std::num::ParseFloatError| {
std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
})?;
row.push(v);
}
if !row.is_empty() {
rows.push(row);
}
}
if rows.is_empty() {
return Ok(ArraysD::F64(
ArrayD::from_shape_vec(IxDyn(&[0]), Vec::<f64>::new()).unwrap_or_default(),
));
}
let cols = rows[0].len();
if rows.iter().all(|r| r.len() == cols) {
if cols == 1 {
let flat: Vec<f64> = rows.into_iter().flatten().collect();
Ok(ArraysD::F64(
ArrayD::from_shape_vec(IxDyn(&[flat.len()]), flat).unwrap_or_default(),
))
} else {
let n = rows.len();
let flat: Vec<f64> = rows.into_iter().flatten().collect();
Ok(ArraysD::F64(
ArrayD::from_shape_vec(IxDyn(&[n, cols]), flat).unwrap_or_default(),
))
}
} else {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"loadtxt: rows have inconsistent column counts",
))
}
}
pub fn tofile(path: &Path, a: &ArraysD) -> std::io::Result<()> {
let mut w = BufWriter::new(File::create(path)?);
write_raw(&mut w, a)?;
w.flush()
}
fn write_raw<W: Write>(w: &mut W, a: &ArraysD) -> std::io::Result<()> {
match a {
ArraysD::Bool(arr) => {
for &v in arr.iter() {
w.write_all(&[v as u8])?;
}
}
ArraysD::I8(arr) => {
for &v in arr.iter() {
w.write_all(&v.to_le_bytes())?;
}
}
ArraysD::I16(arr) => {
for &v in arr.iter() {
w.write_all(&v.to_le_bytes())?;
}
}
ArraysD::I32(arr) => {
for &v in arr.iter() {
w.write_all(&v.to_le_bytes())?;
}
}
ArraysD::I64(arr) => {
for &v in arr.iter() {
w.write_all(&v.to_le_bytes())?;
}
}
ArraysD::U8(arr) => {
if let Some(s) = arr.as_slice() {
w.write_all(s)?;
} else {
for &v in arr.iter() {
w.write_all(&[v])?;
}
}
}
ArraysD::U16(arr) => {
for &v in arr.iter() {
w.write_all(&v.to_le_bytes())?;
}
}
ArraysD::U32(arr) => {
for &v in arr.iter() {
w.write_all(&v.to_le_bytes())?;
}
}
ArraysD::U64(arr) => {
for &v in arr.iter() {
w.write_all(&v.to_le_bytes())?;
}
}
ArraysD::F16(arr) => {
for &v in arr.iter() {
w.write_all(&v.to_le_bytes())?;
}
}
ArraysD::F32(arr) => {
for &v in arr.iter() {
w.write_all(&v.to_le_bytes())?;
}
}
ArraysD::F64(arr) => {
for &v in arr.iter() {
w.write_all(&v.to_le_bytes())?;
}
}
ArraysD::C64(arr) => {
for &v in arr.iter() {
w.write_all(&v.re.to_le_bytes())?;
w.write_all(&v.im.to_le_bytes())?;
}
}
ArraysD::C128(arr) => {
for &v in arr.iter() {
w.write_all(&v.re.to_le_bytes())?;
w.write_all(&v.im.to_le_bytes())?;
}
}
_ => { return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "non-numeric dtype not supported by write_raw")) },
}
Ok(())
}
pub fn fromfile(
path: &Path,
dtype: DType,
count: isize,
) -> std::io::Result<ArraysD> {
let mut f = File::open(path)?;
let mut buf = Vec::new();
f.read_to_end(&mut buf)?;
read_raw(&buf, dtype, count)
}
fn read_raw(buf: &[u8], dtype: DType, count: isize) -> std::io::Result<ArraysD> {
let item = dtype.itemsize();
let available = buf.len() / item;
let n = if count < 0 {
available
} else {
(count as usize).min(available)
};
macro_rules! le {
($ty:ty, $bytes:expr) => {{
let mut v = Vec::<$ty>::with_capacity(n);
for i in 0..n {
let s = i * $bytes;
let b: [u8; $bytes] = buf
.get(s..s + $bytes)
.and_then(|sl| sl.try_into().ok())
.unwrap_or([0; $bytes]);
v.push(<$ty>::from_le_bytes(b));
}
v
}};
}
Ok(match dtype {
DType::Bool => ArraysD::Bool(
ArrayD::from_shape_vec(
IxDyn(&[n]),
buf.get(..n).unwrap_or(&[]).iter().map(|&b| b != 0).collect::<Vec<_>>(),
)
.unwrap_or_default(),
),
DType::I8 => {
let v: Vec<i8> = buf.get(..n).unwrap_or(&[]).iter().map(|&b| b as i8).collect();
ArraysD::I8(ArrayD::from_shape_vec(IxDyn(&[n]), v).unwrap_or_default())
}
DType::U8 => ArraysD::U8(
ArrayD::from_shape_vec(IxDyn(&[n]), buf.get(..n).unwrap_or(&[]).to_vec()).unwrap_or_default(),
),
DType::I16 => ArraysD::I16(ArrayD::from_shape_vec(IxDyn(&[n]), le!(i16, 2)).unwrap_or_default()),
DType::U16 => ArraysD::U16(ArrayD::from_shape_vec(IxDyn(&[n]), le!(u16, 2)).unwrap_or_default()),
DType::F16 => {
let mut v = Vec::<half::f16>::with_capacity(n);
for i in 0..n {
let s = i * 2;
let b: [u8; 2] = buf
.get(s..s + 2)
.and_then(|sl| sl.try_into().ok())
.unwrap_or([0; 2]);
v.push(half::f16::from_le_bytes(b));
}
ArraysD::F16(ArrayD::from_shape_vec(IxDyn(&[n]), v).unwrap_or_default())
}
DType::I32 => ArraysD::I32(ArrayD::from_shape_vec(IxDyn(&[n]), le!(i32, 4)).unwrap_or_default()),
DType::U32 => ArraysD::U32(ArrayD::from_shape_vec(IxDyn(&[n]), le!(u32, 4)).unwrap_or_default()),
DType::F32 => ArraysD::F32(ArrayD::from_shape_vec(IxDyn(&[n]), le!(f32, 4)).unwrap_or_default()),
DType::I64 => ArraysD::I64(ArrayD::from_shape_vec(IxDyn(&[n]), le!(i64, 8)).unwrap_or_default()),
DType::U64 => ArraysD::U64(ArrayD::from_shape_vec(IxDyn(&[n]), le!(u64, 8)).unwrap_or_default()),
DType::F64 => ArraysD::F64(ArrayD::from_shape_vec(IxDyn(&[n]), le!(f64, 8)).unwrap_or_default()),
DType::C64 => {
let mut v = Vec::with_capacity(n);
for i in 0..n {
let s = i * 8;
let re_b: [u8; 4] = buf.get(s..s + 4).and_then(|sl| sl.try_into().ok()).unwrap_or([0; 4]);
let im_b: [u8; 4] = buf.get(s + 4..s + 8).and_then(|sl| sl.try_into().ok()).unwrap_or([0; 4]);
v.push(crate::dtype::C32::new(f32::from_le_bytes(re_b), f32::from_le_bytes(im_b)));
}
ArraysD::C64(ArrayD::from_shape_vec(IxDyn(&[n]), v).unwrap_or_default())
}
DType::C128 => {
let mut v = Vec::with_capacity(n);
for i in 0..n {
let s = i * 16;
let re_b: [u8; 8] = buf.get(s..s + 8).and_then(|sl| sl.try_into().ok()).unwrap_or([0; 8]);
let im_b: [u8; 8] = buf.get(s + 8..s + 16).and_then(|sl| sl.try_into().ok()).unwrap_or([0; 8]);
v.push(crate::dtype::C64::new(f64::from_le_bytes(re_b), f64::from_le_bytes(im_b)));
}
ArraysD::C128(ArrayD::from_shape_vec(IxDyn(&[n]), v).unwrap_or_default())
}
_ => { return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "non-numeric dtype not supported by read_raw")) },
})
}