#[cfg(not(feature = "hdf5"))]
use std::io;
#[cfg(feature = "hdf5")]
use hdf5::{File, Result as Hdf5Result};
#[cfg(feature = "hdf5")]
use crate::vector3::Vector3;
#[cfg(feature = "hdf5")]
pub struct Hdf5Writer {
file: File,
}
#[cfg(feature = "hdf5")]
impl Hdf5Writer {
pub fn create(filename: &str) -> Hdf5Result<Self> {
let file = File::create(filename)?;
Ok(Self { file })
}
pub fn open(filename: &str) -> Hdf5Result<Self> {
let file = File::open_rw(filename)?;
Ok(Self { file })
}
pub fn write_attribute(&self, name: &str, value: &str) -> Hdf5Result<()> {
use std::str::FromStr;
let attr = self
.file
.new_attr::<hdf5::types::VarLenUnicode>()
.create(name)?;
let unicode_value = hdf5::types::VarLenUnicode::from_str(value)
.map_err(|_| hdf5::Error::Internal("String conversion error".to_string()))?;
attr.write_scalar(&unicode_value)?;
Ok(())
}
pub fn write_scalar(&self, path: &str, value: f64) -> Hdf5Result<()> {
self.ensure_groups(path)?;
let dataset = self.file.new_dataset::<f64>().create(path)?;
dataset.write_scalar(&value)?;
Ok(())
}
pub fn write_array(&self, path: &str, data: &[f64]) -> Hdf5Result<()> {
self.ensure_groups(path)?;
let dataset = self
.file
.new_dataset::<f64>()
.shape([data.len()])
.create(path)?;
dataset.write(data)?;
Ok(())
}
pub fn write_array_2d(&self, path: &str, data: &[Vec<f64>]) -> Hdf5Result<()> {
if data.is_empty() {
return Ok(());
}
self.ensure_groups(path)?;
let rows = data.len();
let cols = data[0].len();
let flat: Vec<f64> = data.iter().flat_map(|row| row.iter().copied()).collect();
let dataset = self
.file
.new_dataset::<f64>()
.shape([rows, cols])
.create(path)?;
dataset.write(&flat)?;
Ok(())
}
pub fn write_vector_field(&self, path: &str, vectors: &[Vector3<f64>]) -> Hdf5Result<()> {
self.ensure_groups(path)?;
let data: Vec<f64> = vectors.iter().flat_map(|v| [v.x, v.y, v.z]).collect();
let dataset = self
.file
.new_dataset::<f64>()
.shape([vectors.len(), 3])
.create(path)?;
dataset.write(&data)?;
Ok(())
}
pub fn write_time_series(
&self,
group_path: &str,
times: &[f64],
data: &[(String, Vec<f64>)],
) -> Hdf5Result<()> {
self.file.create_group(group_path)?;
self.write_array(&format!("{}/time", group_path), times)?;
for (name, values) in data {
self.write_array(&format!("{}/{}", group_path, name), values)?;
}
Ok(())
}
fn ensure_groups(&self, path: &str) -> Hdf5Result<()> {
let parts: Vec<&str> = path.split('/').collect();
if parts.len() <= 1 {
return Ok(());
}
let mut current_path = String::new();
for part in &parts[..parts.len() - 1] {
if current_path.is_empty() {
current_path = part.to_string();
} else {
current_path = format!("{}/{}", current_path, part);
}
let _ = self.file.create_group(¤t_path);
}
Ok(())
}
}
#[cfg(feature = "hdf5")]
pub struct Hdf5Reader {
file: File,
}
#[cfg(feature = "hdf5")]
impl Hdf5Reader {
pub fn open(filename: &str) -> Hdf5Result<Self> {
let file = File::open(filename)?;
Ok(Self { file })
}
pub fn read_attribute(&self, name: &str) -> Hdf5Result<String> {
let attr = self.file.attr(name)?;
let value: hdf5::types::VarLenUnicode = attr.read_scalar()?;
Ok(value.to_string())
}
pub fn read_scalar(&self, path: &str) -> Hdf5Result<f64> {
let dataset = self.file.dataset(path)?;
let value: f64 = dataset.read_scalar()?;
Ok(value)
}
pub fn read_array(&self, path: &str) -> Hdf5Result<Vec<f64>> {
let dataset = self.file.dataset(path)?;
let data: Vec<f64> = dataset.read_raw()?;
Ok(data)
}
pub fn read_vector_field(&self, path: &str) -> Hdf5Result<Vec<Vector3<f64>>> {
let dataset = self.file.dataset(path)?;
let shape = dataset.shape();
if shape.len() != 2 || shape[1] != 3 {
return Err(hdf5::Error::from("Invalid vector field shape"));
}
let data: Vec<f64> = dataset.read_raw()?;
let vectors: Vec<Vector3<f64>> = data
.chunks(3)
.map(|chunk| Vector3::new(chunk[0], chunk[1], chunk[2]))
.collect();
Ok(vectors)
}
pub fn list_datasets(&self, group_path: &str) -> Hdf5Result<Vec<String>> {
let group = if group_path.is_empty() || group_path == "/" {
self.file.as_group()?
} else {
self.file.group(group_path)?
};
let names: Vec<String> = group.member_names()?;
Ok(names)
}
}
#[cfg(not(feature = "hdf5"))]
pub struct Hdf5Writer;
#[cfg(not(feature = "hdf5"))]
impl Hdf5Writer {
pub fn create(_filename: &str) -> io::Result<Self> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"HDF5 support not enabled. Compile with --features hdf5",
))
}
}
#[cfg(not(feature = "hdf5"))]
pub struct Hdf5Reader;
#[cfg(not(feature = "hdf5"))]
impl Hdf5Reader {
pub fn open(_filename: &str) -> io::Result<Self> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"HDF5 support not enabled. Compile with --features hdf5",
))
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
#[cfg(not(feature = "hdf5"))]
fn test_hdf5_disabled() {
let result = Hdf5Writer::create("/tmp/test.h5");
assert!(result.is_err());
}
}