use super::functions::*;
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XdmfTopologyType {
Triangle,
Tetrahedron,
Hexahedron,
Mixed,
}
impl XdmfTopologyType {
pub(super) fn as_str(self) -> &'static str {
match self {
Self::Triangle => "Triangle",
Self::Tetrahedron => "Tetrahedron",
Self::Hexahedron => "Hexahedron",
Self::Mixed => "Mixed",
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DeflateMetadata {
pub level: CompressionLevel,
pub shuffle: bool,
pub chunk_shape: Vec<u64>,
pub compressed_size: u64,
pub uncompressed_size: u64,
}
impl DeflateMetadata {
pub fn uncompressed(uncompressed_size: u64) -> Self {
Self {
level: CompressionLevel::None,
shuffle: false,
chunk_shape: Vec::new(),
compressed_size: uncompressed_size,
uncompressed_size,
}
}
pub fn compression_ratio(&self) -> f64 {
if self.compressed_size == 0 {
return 1.0;
}
self.uncompressed_size as f64 / self.compressed_size as f64
}
pub fn space_savings(&self) -> f64 {
if self.uncompressed_size == 0 {
return 0.0;
}
1.0 - self.compressed_size as f64 / self.uncompressed_size as f64
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionLevel {
None,
Fast,
Balanced,
Maximum,
}
impl CompressionLevel {
pub fn level(self) -> u8 {
match self {
Self::None => 0,
Self::Fast => 1,
Self::Balanced => 5,
Self::Maximum => 9,
}
}
pub fn is_compressed(self) -> bool {
!matches!(self, Self::None)
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Dataset {
pub name: String,
pub shape: Vec<usize>,
pub dtype: DataType,
pub data_f64: Vec<f64>,
pub data_i32: Vec<i32>,
pub attributes: Vec<(String, String)>,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct CompressionSettings {
pub algorithm: CompressionAlgorithm,
pub level: u32,
}
#[allow(dead_code)]
impl CompressionSettings {
pub fn none() -> Self {
Self {
algorithm: CompressionAlgorithm::None,
level: 0,
}
}
pub fn delta() -> Self {
Self {
algorithm: CompressionAlgorithm::Delta,
level: 1,
}
}
pub fn delta_encode_f64(data: &[f64]) -> Vec<f64> {
if data.is_empty() {
return Vec::new();
}
let mut encoded = Vec::with_capacity(data.len());
encoded.push(data[0]);
for i in 1..data.len() {
encoded.push(data[i] - data[i - 1]);
}
encoded
}
pub fn delta_decode_f64(encoded: &[f64]) -> Vec<f64> {
if encoded.is_empty() {
return Vec::new();
}
let mut decoded = Vec::with_capacity(encoded.len());
decoded.push(encoded[0]);
for i in 1..encoded.len() {
decoded.push(decoded[i - 1] + encoded[i]);
}
decoded
}
pub fn delta_encode_i32(data: &[i32]) -> Vec<i32> {
if data.is_empty() {
return Vec::new();
}
let mut encoded = Vec::with_capacity(data.len());
encoded.push(data[0]);
for i in 1..data.len() {
encoded.push(data[i] - data[i - 1]);
}
encoded
}
pub fn delta_decode_i32(encoded: &[i32]) -> Vec<i32> {
if encoded.is_empty() {
return Vec::new();
}
let mut decoded = Vec::with_capacity(encoded.len());
decoded.push(encoded[0]);
for i in 1..encoded.len() {
decoded.push(decoded[i - 1] + encoded[i]);
}
decoded
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct CompoundField {
pub name: String,
pub dtype: DataType,
pub values: Vec<f64>,
}
impl CompoundField {
pub fn new(name: impl Into<String>, dtype: DataType, values: Vec<f64>) -> Self {
Self {
name: name.into(),
dtype,
values,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(dead_code)]
pub enum CompressionAlgorithm {
None,
RunLength,
Delta,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct CompoundDataset {
pub name: String,
pub n_records: usize,
pub fields: Vec<CompoundField>,
pub attrs: Vec<(String, String)>,
}
impl CompoundDataset {
pub fn new(name: impl Into<String>, n_records: usize) -> Self {
Self {
name: name.into(),
n_records,
fields: Vec::new(),
attrs: Vec::new(),
}
}
pub fn add_field(&mut self, field: CompoundField) {
assert_eq!(
field.values.len(),
self.n_records,
"Field {} has {} values, expected {}",
field.name,
field.values.len(),
self.n_records
);
self.fields.push(field);
}
pub fn add_attr(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.attrs.push((key.into(), value.into()));
}
pub fn get_field(&self, name: &str) -> Option<&[f64]> {
self.fields
.iter()
.find(|f| f.name == name)
.map(|f| f.values.as_slice())
}
pub fn n_fields(&self) -> usize {
self.fields.len()
}
pub fn to_csv_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
let header: Vec<&str> = self.fields.iter().map(|f| f.name.as_str()).collect();
out.extend_from_slice(header.join(",").as_bytes());
out.push(b'\n');
for rec in 0..self.n_records {
let vals: Vec<String> = self
.fields
.iter()
.map(|f| format!("{:.6}", f.values[rec]))
.collect();
out.extend_from_slice(vals.join(",").as_bytes());
out.push(b'\n');
}
out
}
}
#[derive(Debug, Clone)]
pub struct DatasetStats {
pub count: usize,
pub min: f64,
pub max: f64,
pub mean: f64,
pub variance: f64,
}
impl DatasetStats {
pub fn from_slice(data: &[f64]) -> Option<Self> {
if data.is_empty() {
return None;
}
let count = data.len();
let mut min = data[0];
let mut max = data[0];
let mut sum = 0.0_f64;
for &v in data {
if v < min {
min = v;
}
if v > max {
max = v;
}
sum += v;
}
let mean = sum / count as f64;
let variance = data.iter().map(|&v| (v - mean) * (v - mean)).sum::<f64>() / count as f64;
Some(Self {
count,
min,
max,
mean,
variance,
})
}
pub fn std_dev(&self) -> f64 {
self.variance.sqrt()
}
pub fn range(&self) -> f64 {
self.max - self.min
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ShdfSchema {
pub expected_datasets: Vec<(String, DataType)>,
pub required_attributes: Vec<String>,
}
#[allow(dead_code)]
impl ShdfSchema {
pub fn new() -> Self {
Self {
expected_datasets: Vec::new(),
required_attributes: Vec::new(),
}
}
pub fn expect_dataset(&mut self, name: &str, dtype: DataType) {
self.expected_datasets.push((name.to_string(), dtype));
}
pub fn require_attribute(&mut self, key: &str) {
self.required_attributes.push(key.to_string());
}
pub fn validate(&self, file: &ShdfFile) -> Vec<String> {
let mut errors = Vec::new();
for (name, dtype) in &self.expected_datasets {
match file.datasets.iter().find(|d| &d.name == name) {
None => errors.push(format!("Missing dataset: {name}")),
Some(ds) => {
if ds.dtype != *dtype {
errors.push(format!(
"Dataset '{name}': expected {:?}, got {:?}",
dtype, ds.dtype
));
}
}
}
}
for key in &self.required_attributes {
if !file.global_attributes.iter().any(|(k, _)| k == key) {
errors.push(format!("Missing global attribute: {key}"));
}
}
errors
}
}
impl Default for ShdfSchema {
fn default() -> Self {
Self::new()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ChunkDescriptor {
pub shape: Vec<u64>,
pub offset: Vec<u64>,
pub index: u64,
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub enum DataType {
Float64,
Float32,
Int32,
Int64,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ChunkedDataset {
pub name: String,
pub dims: Vec<u64>,
pub chunk_shape: Vec<u64>,
pub data: Vec<f64>,
pub attrs: Vec<(String, String)>,
}
impl ChunkedDataset {
pub fn new(name: impl Into<String>, dims: Vec<u64>, chunk_shape: Vec<u64>) -> Self {
let total: u64 = dims.iter().product();
Self {
name: name.into(),
dims,
chunk_shape,
data: vec![0.0; total as usize],
attrs: Vec::new(),
}
}
pub fn n_elements(&self) -> usize {
self.dims.iter().product::<u64>() as usize
}
pub fn n_chunks_per_dim(&self) -> Vec<u64> {
self.dims
.iter()
.zip(self.chunk_shape.iter())
.map(|(&d, &c)| d.div_ceil(c))
.collect()
}
pub fn total_chunks(&self) -> u64 {
self.n_chunks_per_dim().iter().product()
}
pub fn write_chunk_1d(&mut self, chunk_idx: u64, chunk_data: &[f64]) {
let chunk_size = self.chunk_shape[0] as usize;
let start = (chunk_idx as usize) * chunk_size;
let end = (start + chunk_data.len()).min(self.data.len());
let src_end = end - start;
self.data[start..end].copy_from_slice(&chunk_data[..src_end]);
}
pub fn add_attr(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.attrs.push((key.into(), value.into()));
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
let name_bytes = self.name.as_bytes();
buf.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
buf.extend_from_slice(name_bytes);
buf.push(0u8);
buf.extend_from_slice(&(self.dims.len() as u32).to_le_bytes());
for &d in &self.dims {
buf.extend_from_slice(&d.to_le_bytes());
}
let n = self.n_elements() as u64;
buf.extend_from_slice(&n.to_le_bytes());
for &v in &self.data {
buf.extend_from_slice(&v.to_le_bytes());
}
buf.extend_from_slice(&(self.attrs.len() as u32).to_le_bytes());
for (k, v) in &self.attrs {
let kb = k.as_bytes();
buf.extend_from_slice(&(kb.len() as u32).to_le_bytes());
buf.extend_from_slice(kb);
let vb = v.as_bytes();
buf.extend_from_slice(&(vb.len() as u32).to_le_bytes());
buf.extend_from_slice(vb);
}
buf
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct XdmfParams {
pub hdf5_path: String,
pub coords_dataset: String,
pub connectivity_dataset: String,
pub n_nodes: usize,
pub n_elements: usize,
pub nodes_per_element: usize,
pub topology: XdmfTopologyType,
pub attributes: Vec<(String, String)>,
}
pub struct GroupNavigator {
pub root: ShdfGroup,
}
impl GroupNavigator {
pub fn new(root: ShdfGroup) -> Self {
Self { root }
}
pub fn get_dataset(&self, path: &str) -> Option<&Dataset> {
let parts: Vec<&str> = path.trim_start_matches('/').splitn(64, '/').collect();
if parts.is_empty() {
return None;
}
let (ds_name, group_parts) = parts.split_last()?;
let mut group = &self.root;
let effective_parts = if group_parts.first().copied() == Some(self.root.name.as_str()) {
&group_parts[1..]
} else {
group_parts
};
for &part in effective_parts {
group = group.get_child(part)?;
}
group.get_dataset(ds_name)
}
pub fn all_paths(&self) -> Vec<String> {
let mut result = Vec::new();
Self::collect_paths(&self.root, "", &mut result);
result
}
fn collect_paths(group: &ShdfGroup, prefix: &str, out: &mut Vec<String>) {
let base = if prefix.is_empty() {
format!("/{}", group.name)
} else {
format!("{}/{}", prefix, group.name)
};
for ds in &group.datasets {
out.push(format!("{}/{}", base, ds.name));
}
for child in &group.children {
Self::collect_paths(child, &base, out);
}
}
pub fn total_datasets(&self) -> usize {
self.root.total_datasets()
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ChunkingConfig {
pub chunk_dims: Vec<usize>,
}
#[allow(dead_code)]
impl ChunkingConfig {
pub fn new(chunk_dims: Vec<usize>) -> Self {
Self { chunk_dims }
}
pub fn n_chunks(&self, shape: &[usize]) -> usize {
if shape.len() != self.chunk_dims.len() {
return 0;
}
let mut total = 1_usize;
for (s, c) in shape.iter().zip(self.chunk_dims.iter()) {
if *c == 0 {
return 0;
}
total *= (*s).div_ceil(*c);
}
total
}
pub fn chunk_index(&self, element_idx: &[usize], shape: &[usize]) -> usize {
if shape.len() != self.chunk_dims.len() || element_idx.len() != shape.len() {
return 0;
}
let mut idx = 0;
let mut stride = 1;
for d in (0..shape.len()).rev() {
let chunk_pos = element_idx[d] / self.chunk_dims[d].max(1);
let n_chunks_d = (shape[d] + self.chunk_dims[d] - 1) / self.chunk_dims[d].max(1);
idx += chunk_pos * stride;
stride *= n_chunks_d;
}
idx
}
pub fn default_for_shape(shape: &[usize]) -> Self {
let chunk_dims: Vec<usize> = shape.iter().map(|&s| s.min(64)).collect();
Self { chunk_dims }
}
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub enum AttributeValue {
String(String),
Float64(f64),
Int32(i32),
Bool(bool),
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ShdfGroup {
pub name: String,
pub datasets: Vec<Dataset>,
pub children: Vec<ShdfGroup>,
pub attributes: Vec<(String, String)>,
}
#[allow(dead_code)]
impl ShdfGroup {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
datasets: Vec::new(),
children: Vec::new(),
attributes: Vec::new(),
}
}
pub fn add_dataset_f64(&mut self, name: &str, shape: Vec<usize>, data: Vec<f64>) {
self.datasets.push(Dataset {
name: name.to_string(),
shape,
dtype: DataType::Float64,
data_f64: data,
data_i32: Vec::new(),
attributes: Vec::new(),
});
}
pub fn add_dataset_i32(&mut self, name: &str, shape: Vec<usize>, data: Vec<i32>) {
self.datasets.push(Dataset {
name: name.to_string(),
shape,
dtype: DataType::Int32,
data_f64: Vec::new(),
data_i32: data,
attributes: Vec::new(),
});
}
pub fn add_child(&mut self, child: ShdfGroup) {
self.children.push(child);
}
pub fn add_attribute(&mut self, key: &str, value: &str) {
self.attributes.push((key.to_string(), value.to_string()));
}
pub fn get_dataset(&self, name: &str) -> Option<&Dataset> {
self.datasets.iter().find(|d| d.name == name)
}
pub fn get_child(&self, name: &str) -> Option<&ShdfGroup> {
self.children.iter().find(|c| c.name == name)
}
pub fn total_datasets(&self) -> usize {
self.datasets.len()
+ self
.children
.iter()
.map(|c| c.total_datasets())
.sum::<usize>()
}
pub fn summary(&self, indent: usize) -> String {
let prefix = " ".repeat(indent);
let mut out = format!("{prefix}Group: {}\n", self.name);
for (k, v) in &self.attributes {
out.push_str(&format!("{prefix} attr: {k} = {v}\n"));
}
for ds in &self.datasets {
let shape_str: Vec<String> = ds.shape.iter().map(|s| s.to_string()).collect();
out.push_str(&format!(
"{prefix} Dataset: {} shape=[{}] dtype={:?}\n",
ds.name,
shape_str.join("x"),
ds.dtype,
));
}
for child in &self.children {
out.push_str(&child.summary(indent + 2));
}
out
}
}
#[allow(dead_code)]
pub struct TimeSeriesAppender {
pub name: String,
pub data: Vec<f64>,
pub n_frames: usize,
pub frame_width: usize,
}
impl TimeSeriesAppender {
pub fn new(name: impl Into<String>, frame_width: usize) -> Self {
Self {
name: name.into(),
data: Vec::new(),
n_frames: 0,
frame_width,
}
}
pub fn append(&mut self, frame: &[f64]) {
assert_eq!(
frame.len(),
self.frame_width,
"frame length {} != frame_width {}",
frame.len(),
self.frame_width
);
self.data.extend_from_slice(frame);
self.n_frames += 1;
}
pub fn total_samples(&self) -> usize {
self.data.len()
}
pub fn get_frame(&self, idx: usize) -> Option<&[f64]> {
let start = idx * self.frame_width;
let end = start + self.frame_width;
self.data.get(start..end)
}
pub fn to_dataset(&self) -> Dataset {
let mut ds = Dataset {
name: self.name.clone(),
dtype: DataType::Float64,
shape: vec![self.n_frames, self.frame_width],
data_f64: self.data.clone(),
data_i32: Vec::new(),
attributes: Vec::new(),
};
ds.attributes
.push(("n_frames".to_string(), self.n_frames.to_string()));
ds.attributes
.push(("frame_width".to_string(), self.frame_width.to_string()));
ds
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct VirtualLink {
pub virtual_path: String,
pub source_file: String,
pub source_path: String,
pub slices: Vec<[usize; 3]>,
}
impl VirtualLink {
pub fn new(
virtual_path: impl Into<String>,
source_file: impl Into<String>,
source_path: impl Into<String>,
) -> Self {
Self {
virtual_path: virtual_path.into(),
source_file: source_file.into(),
source_path: source_path.into(),
slices: Vec::new(),
}
}
pub fn with_slice(mut self, start: usize, stop: usize, step: usize) -> Self {
self.slices.push([start, stop, step]);
self
}
pub fn to_cdl(&self) -> String {
let slice_str = if self.slices.is_empty() {
"(:)".to_string()
} else {
let parts: Vec<String> = self
.slices
.iter()
.map(|s| format!("{}:{}:{}", s[0], s[1], s[2]))
.collect();
format!("({})", parts.join(", "))
};
format!(
"{} -> {}:{}{}",
self.virtual_path, self.source_file, self.source_path, slice_str
)
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ShdfFile {
pub datasets: Vec<Dataset>,
pub global_attributes: Vec<(String, String)>,
}
impl ShdfFile {
#[allow(dead_code)]
pub fn new() -> Self {
ShdfFile {
datasets: Vec::new(),
global_attributes: Vec::new(),
}
}
#[allow(dead_code)]
pub fn add_dataset_f64(&mut self, name: &str, shape: Vec<usize>, data: Vec<f64>) {
self.datasets.push(Dataset {
name: name.to_string(),
shape,
dtype: DataType::Float64,
data_f64: data,
data_i32: Vec::new(),
attributes: Vec::new(),
});
}
#[allow(dead_code)]
pub fn add_dataset_i32(&mut self, name: &str, shape: Vec<usize>, data: Vec<i32>) {
self.datasets.push(Dataset {
name: name.to_string(),
shape,
dtype: DataType::Int32,
data_f64: Vec::new(),
data_i32: data,
attributes: Vec::new(),
});
}
#[allow(dead_code)]
pub fn add_global_attr(&mut self, key: &str, value: &str) {
self.global_attributes
.push((key.to_string(), value.to_string()));
}
#[allow(dead_code)]
pub fn get_f64(&self, name: &str) -> Option<&[f64]> {
self.datasets
.iter()
.find(|d| d.name == name)
.map(|d| d.data_f64.as_slice())
}
#[allow(dead_code)]
pub fn get_i32(&self, name: &str) -> Option<&[i32]> {
self.datasets
.iter()
.find(|d| d.name == name)
.map(|d| d.data_i32.as_slice())
}
#[allow(dead_code)]
pub fn to_bytes(&self) -> Vec<u8> {
let mut out: Vec<u8> = Vec::new();
out.extend_from_slice(MAGIC);
out.extend_from_slice(&VERSION.to_le_bytes());
out.extend_from_slice(&(self.global_attributes.len() as u32).to_le_bytes());
for (k, v) in &self.global_attributes {
out.extend_from_slice(&encode_string(k));
out.extend_from_slice(&encode_string(v));
}
out.extend_from_slice(&(self.datasets.len() as u32).to_le_bytes());
for ds in &self.datasets {
out.extend_from_slice(&encode_string(&ds.name));
let dtype_byte: u8 = match ds.dtype {
DataType::Float64 => 0,
DataType::Float32 => 1,
DataType::Int32 => 2,
DataType::Int64 => 3,
};
out.push(dtype_byte);
out.extend_from_slice(&(ds.shape.len() as u32).to_le_bytes());
for &dim in &ds.shape {
out.extend_from_slice(&(dim as u64).to_le_bytes());
}
match ds.dtype {
DataType::Float64 => {
out.extend_from_slice(&(ds.data_f64.len() as u64).to_le_bytes());
for &v in &ds.data_f64 {
out.extend_from_slice(&v.to_le_bytes());
}
}
DataType::Float32 => {
out.extend_from_slice(&(ds.data_f64.len() as u64).to_le_bytes());
for &v in &ds.data_f64 {
out.extend_from_slice(&(v as f32).to_le_bytes());
}
}
DataType::Int32 => {
out.extend_from_slice(&(ds.data_i32.len() as u64).to_le_bytes());
for &v in &ds.data_i32 {
out.extend_from_slice(&v.to_le_bytes());
}
}
DataType::Int64 => {
out.extend_from_slice(&(ds.data_i32.len() as u64).to_le_bytes());
for &v in &ds.data_i32 {
out.extend_from_slice(&(v as i64).to_le_bytes());
}
}
}
out.extend_from_slice(&(ds.attributes.len() as u32).to_le_bytes());
for (k, v) in &ds.attributes {
out.extend_from_slice(&encode_string(k));
out.extend_from_slice(&encode_string(v));
}
}
out
}
#[allow(dead_code)]
pub fn from_bytes(data: &[u8]) -> Result<Self, String> {
let mut pos: usize = 0;
if data.len() < 4 {
return Err("too short for magic".to_string());
}
if &data[pos..pos + 4] != MAGIC {
return Err(format!("bad magic: {:?}", &data[pos..pos + 4]));
}
pos += 4;
let version = read_u32(data, &mut pos)?;
if version != VERSION {
return Err(format!("unsupported version: {version}"));
}
let n_global = read_u32(data, &mut pos)? as usize;
let mut global_attributes = Vec::with_capacity(n_global);
for _ in 0..n_global {
let k = decode_string(data, &mut pos)?;
let v = decode_string(data, &mut pos)?;
global_attributes.push((k, v));
}
let n_datasets = read_u32(data, &mut pos)? as usize;
let mut datasets = Vec::with_capacity(n_datasets);
for _ in 0..n_datasets {
let name = decode_string(data, &mut pos)?;
let dtype_byte = read_u8(data, &mut pos)?;
let dtype = match dtype_byte {
0 => DataType::Float64,
1 => DataType::Float32,
2 => DataType::Int32,
3 => DataType::Int64,
_ => return Err(format!("unknown dtype byte: {dtype_byte}")),
};
let n_dims = read_u32(data, &mut pos)? as usize;
let mut shape = Vec::with_capacity(n_dims);
for _ in 0..n_dims {
shape.push(read_u64(data, &mut pos)? as usize);
}
let n_elems = read_u64(data, &mut pos)? as usize;
let mut data_f64 = Vec::new();
let mut data_i32 = Vec::new();
match dtype {
DataType::Float64 => {
data_f64.reserve(n_elems);
for _ in 0..n_elems {
data_f64.push(read_f64(data, &mut pos)?);
}
}
DataType::Float32 => {
data_f64.reserve(n_elems);
for _ in 0..n_elems {
data_f64.push(read_f32(data, &mut pos)? as f64);
}
}
DataType::Int32 => {
data_i32.reserve(n_elems);
for _ in 0..n_elems {
data_i32.push(read_i32(data, &mut pos)?);
}
}
DataType::Int64 => {
data_i32.reserve(n_elems);
for _ in 0..n_elems {
data_i32.push(read_i64(data, &mut pos)? as i32);
}
}
}
let n_attrs = read_u32(data, &mut pos)? as usize;
let mut attributes = Vec::with_capacity(n_attrs);
for _ in 0..n_attrs {
let k = decode_string(data, &mut pos)?;
let v = decode_string(data, &mut pos)?;
attributes.push((k, v));
}
datasets.push(Dataset {
name,
shape,
dtype,
data_f64,
data_i32,
attributes,
});
}
Ok(ShdfFile {
datasets,
global_attributes,
})
}
#[allow(dead_code)]
pub fn write_to_text(&self) -> String {
let mut out = String::new();
out.push_str("=== SHDF File Summary ===\n");
if !self.global_attributes.is_empty() {
out.push_str("Global attributes:\n");
for (k, v) in &self.global_attributes {
out.push_str(&format!(" {k} = {v}\n"));
}
}
out.push_str(&format!("Datasets: {}\n", self.datasets.len()));
for ds in &self.datasets {
let shape_str: Vec<String> = ds.shape.iter().map(|s| s.to_string()).collect();
out.push_str(&format!(
" [{}] shape=[{}] dtype={:?}\n",
ds.name,
shape_str.join("×"),
ds.dtype,
));
let preview = match ds.dtype {
DataType::Float64 | DataType::Float32 => {
let vals: Vec<String> = ds
.data_f64
.iter()
.take(5)
.map(|v| format!("{v:.6}"))
.collect();
vals.join(", ")
}
DataType::Int32 | DataType::Int64 => {
let vals: Vec<String> =
ds.data_i32.iter().take(5).map(|v| v.to_string()).collect();
vals.join(", ")
}
};
if !preview.is_empty() {
out.push_str(&format!(" first values: [{preview}]\n"));
}
if !ds.attributes.is_empty() {
out.push_str(" attributes:\n");
for (k, v) in &ds.attributes {
out.push_str(&format!(" {k} = {v}\n"));
}
}
}
out
}
}
impl Default for ShdfFile {
fn default() -> Self {
Self::new()
}
}
#[allow(dead_code)]
pub struct AttributeHelper;
#[allow(dead_code)]
impl AttributeHelper {
pub fn to_string(val: &AttributeValue) -> String {
match val {
AttributeValue::String(s) => format!("s:{s}"),
AttributeValue::Float64(f) => format!("f:{f}"),
AttributeValue::Int32(i) => format!("i:{i}"),
AttributeValue::Bool(b) => format!("b:{b}"),
}
}
pub fn from_string(s: &str) -> AttributeValue {
if let Some(rest) = s.strip_prefix("f:")
&& let Ok(f) = rest.parse::<f64>()
{
return AttributeValue::Float64(f);
}
if let Some(rest) = s.strip_prefix("i:")
&& let Ok(i) = rest.parse::<i32>()
{
return AttributeValue::Int32(i);
}
if let Some(rest) = s.strip_prefix("b:") {
return AttributeValue::Bool(rest == "true");
}
if let Some(rest) = s.strip_prefix("s:") {
return AttributeValue::String(rest.to_string());
}
AttributeValue::String(s.to_string())
}
}