1use super::functions::*;
6
7#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum XdmfTopologyType {
11 Triangle,
13 Tetrahedron,
15 Hexahedron,
17 Mixed,
19}
20impl XdmfTopologyType {
21 pub(super) fn as_str(self) -> &'static str {
22 match self {
23 Self::Triangle => "Triangle",
24 Self::Tetrahedron => "Tetrahedron",
25 Self::Hexahedron => "Hexahedron",
26 Self::Mixed => "Mixed",
27 }
28 }
29}
30#[allow(dead_code)]
32#[derive(Debug, Clone)]
33pub struct DeflateMetadata {
34 pub level: CompressionLevel,
36 pub shuffle: bool,
38 pub chunk_shape: Vec<u64>,
40 pub compressed_size: u64,
42 pub uncompressed_size: u64,
44}
45impl DeflateMetadata {
46 pub fn uncompressed(uncompressed_size: u64) -> Self {
48 Self {
49 level: CompressionLevel::None,
50 shuffle: false,
51 chunk_shape: Vec::new(),
52 compressed_size: uncompressed_size,
53 uncompressed_size,
54 }
55 }
56 pub fn compression_ratio(&self) -> f64 {
58 if self.compressed_size == 0 {
59 return 1.0;
60 }
61 self.uncompressed_size as f64 / self.compressed_size as f64
62 }
63 pub fn space_savings(&self) -> f64 {
65 if self.uncompressed_size == 0 {
66 return 0.0;
67 }
68 1.0 - self.compressed_size as f64 / self.uncompressed_size as f64
69 }
70}
71#[allow(dead_code)]
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum CompressionLevel {
75 None,
77 Fast,
79 Balanced,
81 Maximum,
83}
84impl CompressionLevel {
85 pub fn level(self) -> u8 {
87 match self {
88 Self::None => 0,
89 Self::Fast => 1,
90 Self::Balanced => 5,
91 Self::Maximum => 9,
92 }
93 }
94 pub fn is_compressed(self) -> bool {
96 !matches!(self, Self::None)
97 }
98}
99#[derive(Debug, Clone)]
101#[allow(dead_code)]
102pub struct Dataset {
103 pub name: String,
105 pub shape: Vec<usize>,
107 pub dtype: DataType,
109 pub data_f64: Vec<f64>,
111 pub data_i32: Vec<i32>,
113 pub attributes: Vec<(String, String)>,
115}
116#[derive(Debug, Clone)]
118#[allow(dead_code)]
119pub struct CompressionSettings {
120 pub algorithm: CompressionAlgorithm,
122 pub level: u32,
124}
125#[allow(dead_code)]
126impl CompressionSettings {
127 pub fn none() -> Self {
129 Self {
130 algorithm: CompressionAlgorithm::None,
131 level: 0,
132 }
133 }
134 pub fn delta() -> Self {
136 Self {
137 algorithm: CompressionAlgorithm::Delta,
138 level: 1,
139 }
140 }
141 pub fn delta_encode_f64(data: &[f64]) -> Vec<f64> {
143 if data.is_empty() {
144 return Vec::new();
145 }
146 let mut encoded = Vec::with_capacity(data.len());
147 encoded.push(data[0]);
148 for i in 1..data.len() {
149 encoded.push(data[i] - data[i - 1]);
150 }
151 encoded
152 }
153 pub fn delta_decode_f64(encoded: &[f64]) -> Vec<f64> {
155 if encoded.is_empty() {
156 return Vec::new();
157 }
158 let mut decoded = Vec::with_capacity(encoded.len());
159 decoded.push(encoded[0]);
160 for i in 1..encoded.len() {
161 decoded.push(decoded[i - 1] + encoded[i]);
162 }
163 decoded
164 }
165 pub fn delta_encode_i32(data: &[i32]) -> Vec<i32> {
167 if data.is_empty() {
168 return Vec::new();
169 }
170 let mut encoded = Vec::with_capacity(data.len());
171 encoded.push(data[0]);
172 for i in 1..data.len() {
173 encoded.push(data[i] - data[i - 1]);
174 }
175 encoded
176 }
177 pub fn delta_decode_i32(encoded: &[i32]) -> Vec<i32> {
179 if encoded.is_empty() {
180 return Vec::new();
181 }
182 let mut decoded = Vec::with_capacity(encoded.len());
183 decoded.push(encoded[0]);
184 for i in 1..encoded.len() {
185 decoded.push(decoded[i - 1] + encoded[i]);
186 }
187 decoded
188 }
189}
190#[allow(dead_code)]
192#[derive(Debug, Clone)]
193pub struct CompoundField {
194 pub name: String,
196 pub dtype: DataType,
198 pub values: Vec<f64>,
200}
201impl CompoundField {
202 pub fn new(name: impl Into<String>, dtype: DataType, values: Vec<f64>) -> Self {
204 Self {
205 name: name.into(),
206 dtype,
207 values,
208 }
209 }
210}
211#[derive(Debug, Clone, Copy, PartialEq)]
213#[allow(dead_code)]
214pub enum CompressionAlgorithm {
215 None,
217 RunLength,
219 Delta,
221}
222#[allow(dead_code)]
224#[derive(Debug, Clone)]
225pub struct CompoundDataset {
226 pub name: String,
228 pub n_records: usize,
230 pub fields: Vec<CompoundField>,
232 pub attrs: Vec<(String, String)>,
234}
235impl CompoundDataset {
236 pub fn new(name: impl Into<String>, n_records: usize) -> Self {
238 Self {
239 name: name.into(),
240 n_records,
241 fields: Vec::new(),
242 attrs: Vec::new(),
243 }
244 }
245 pub fn add_field(&mut self, field: CompoundField) {
247 assert_eq!(
248 field.values.len(),
249 self.n_records,
250 "Field {} has {} values, expected {}",
251 field.name,
252 field.values.len(),
253 self.n_records
254 );
255 self.fields.push(field);
256 }
257 pub fn add_attr(&mut self, key: impl Into<String>, value: impl Into<String>) {
259 self.attrs.push((key.into(), value.into()));
260 }
261 pub fn get_field(&self, name: &str) -> Option<&[f64]> {
263 self.fields
264 .iter()
265 .find(|f| f.name == name)
266 .map(|f| f.values.as_slice())
267 }
268 pub fn n_fields(&self) -> usize {
270 self.fields.len()
271 }
272 pub fn to_csv_bytes(&self) -> Vec<u8> {
274 let mut out = Vec::new();
275 let header: Vec<&str> = self.fields.iter().map(|f| f.name.as_str()).collect();
276 out.extend_from_slice(header.join(",").as_bytes());
277 out.push(b'\n');
278 for rec in 0..self.n_records {
279 let vals: Vec<String> = self
280 .fields
281 .iter()
282 .map(|f| format!("{:.6}", f.values[rec]))
283 .collect();
284 out.extend_from_slice(vals.join(",").as_bytes());
285 out.push(b'\n');
286 }
287 out
288 }
289}
290#[derive(Debug, Clone)]
292pub struct DatasetStats {
293 pub count: usize,
295 pub min: f64,
297 pub max: f64,
299 pub mean: f64,
301 pub variance: f64,
303}
304impl DatasetStats {
305 pub fn from_slice(data: &[f64]) -> Option<Self> {
307 if data.is_empty() {
308 return None;
309 }
310 let count = data.len();
311 let mut min = data[0];
312 let mut max = data[0];
313 let mut sum = 0.0_f64;
314 for &v in data {
315 if v < min {
316 min = v;
317 }
318 if v > max {
319 max = v;
320 }
321 sum += v;
322 }
323 let mean = sum / count as f64;
324 let variance = data.iter().map(|&v| (v - mean) * (v - mean)).sum::<f64>() / count as f64;
325 Some(Self {
326 count,
327 min,
328 max,
329 mean,
330 variance,
331 })
332 }
333 pub fn std_dev(&self) -> f64 {
335 self.variance.sqrt()
336 }
337 pub fn range(&self) -> f64 {
339 self.max - self.min
340 }
341}
342#[derive(Debug, Clone)]
344#[allow(dead_code)]
345pub struct ShdfSchema {
346 pub expected_datasets: Vec<(String, DataType)>,
348 pub required_attributes: Vec<String>,
350}
351#[allow(dead_code)]
352impl ShdfSchema {
353 pub fn new() -> Self {
355 Self {
356 expected_datasets: Vec::new(),
357 required_attributes: Vec::new(),
358 }
359 }
360 pub fn expect_dataset(&mut self, name: &str, dtype: DataType) {
362 self.expected_datasets.push((name.to_string(), dtype));
363 }
364 pub fn require_attribute(&mut self, key: &str) {
366 self.required_attributes.push(key.to_string());
367 }
368 pub fn validate(&self, file: &ShdfFile) -> Vec<String> {
372 let mut errors = Vec::new();
373 for (name, dtype) in &self.expected_datasets {
374 match file.datasets.iter().find(|d| &d.name == name) {
375 None => errors.push(format!("Missing dataset: {name}")),
376 Some(ds) => {
377 if ds.dtype != *dtype {
378 errors.push(format!(
379 "Dataset '{name}': expected {:?}, got {:?}",
380 dtype, ds.dtype
381 ));
382 }
383 }
384 }
385 }
386 for key in &self.required_attributes {
387 if !file.global_attributes.iter().any(|(k, _)| k == key) {
388 errors.push(format!("Missing global attribute: {key}"));
389 }
390 }
391 errors
392 }
393}
394impl Default for ShdfSchema {
395 fn default() -> Self {
396 Self::new()
397 }
398}
399#[allow(dead_code)]
401#[derive(Debug, Clone)]
402pub struct ChunkDescriptor {
403 pub shape: Vec<u64>,
405 pub offset: Vec<u64>,
407 pub index: u64,
409}
410#[derive(Debug, Clone, PartialEq)]
412#[allow(dead_code)]
413pub enum DataType {
414 Float64,
416 Float32,
418 Int32,
420 Int64,
422}
423#[allow(dead_code)]
425#[derive(Debug, Clone)]
426pub struct ChunkedDataset {
427 pub name: String,
429 pub dims: Vec<u64>,
431 pub chunk_shape: Vec<u64>,
433 pub data: Vec<f64>,
435 pub attrs: Vec<(String, String)>,
437}
438impl ChunkedDataset {
439 pub fn new(name: impl Into<String>, dims: Vec<u64>, chunk_shape: Vec<u64>) -> Self {
441 let total: u64 = dims.iter().product();
442 Self {
443 name: name.into(),
444 dims,
445 chunk_shape,
446 data: vec![0.0; total as usize],
447 attrs: Vec::new(),
448 }
449 }
450 pub fn n_elements(&self) -> usize {
452 self.dims.iter().product::<u64>() as usize
453 }
454 pub fn n_chunks_per_dim(&self) -> Vec<u64> {
456 self.dims
457 .iter()
458 .zip(self.chunk_shape.iter())
459 .map(|(&d, &c)| d.div_ceil(c))
460 .collect()
461 }
462 pub fn total_chunks(&self) -> u64 {
464 self.n_chunks_per_dim().iter().product()
465 }
466 pub fn write_chunk_1d(&mut self, chunk_idx: u64, chunk_data: &[f64]) {
468 let chunk_size = self.chunk_shape[0] as usize;
469 let start = (chunk_idx as usize) * chunk_size;
470 let end = (start + chunk_data.len()).min(self.data.len());
471 let src_end = end - start;
472 self.data[start..end].copy_from_slice(&chunk_data[..src_end]);
473 }
474 pub fn add_attr(&mut self, key: impl Into<String>, value: impl Into<String>) {
476 self.attrs.push((key.into(), value.into()));
477 }
478 pub fn to_bytes(&self) -> Vec<u8> {
480 let mut buf = Vec::new();
481 let name_bytes = self.name.as_bytes();
482 buf.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
483 buf.extend_from_slice(name_bytes);
484 buf.push(0u8);
485 buf.extend_from_slice(&(self.dims.len() as u32).to_le_bytes());
486 for &d in &self.dims {
487 buf.extend_from_slice(&d.to_le_bytes());
488 }
489 let n = self.n_elements() as u64;
490 buf.extend_from_slice(&n.to_le_bytes());
491 for &v in &self.data {
492 buf.extend_from_slice(&v.to_le_bytes());
493 }
494 buf.extend_from_slice(&(self.attrs.len() as u32).to_le_bytes());
495 for (k, v) in &self.attrs {
496 let kb = k.as_bytes();
497 buf.extend_from_slice(&(kb.len() as u32).to_le_bytes());
498 buf.extend_from_slice(kb);
499 let vb = v.as_bytes();
500 buf.extend_from_slice(&(vb.len() as u32).to_le_bytes());
501 buf.extend_from_slice(vb);
502 }
503 buf
504 }
505}
506#[allow(dead_code)]
508#[derive(Debug, Clone)]
509pub struct XdmfParams {
510 pub hdf5_path: String,
512 pub coords_dataset: String,
514 pub connectivity_dataset: String,
516 pub n_nodes: usize,
518 pub n_elements: usize,
520 pub nodes_per_element: usize,
522 pub topology: XdmfTopologyType,
524 pub attributes: Vec<(String, String)>,
526}
527pub struct GroupNavigator {
540 pub root: ShdfGroup,
542}
543impl GroupNavigator {
544 pub fn new(root: ShdfGroup) -> Self {
546 Self { root }
547 }
548 pub fn get_dataset(&self, path: &str) -> Option<&Dataset> {
553 let parts: Vec<&str> = path.trim_start_matches('/').splitn(64, '/').collect();
554 if parts.is_empty() {
555 return None;
556 }
557 let (ds_name, group_parts) = parts.split_last()?;
558 let mut group = &self.root;
559 let effective_parts = if group_parts.first().copied() == Some(self.root.name.as_str()) {
560 &group_parts[1..]
561 } else {
562 group_parts
563 };
564 for &part in effective_parts {
565 group = group.get_child(part)?;
566 }
567 group.get_dataset(ds_name)
568 }
569 pub fn all_paths(&self) -> Vec<String> {
571 let mut result = Vec::new();
572 Self::collect_paths(&self.root, "", &mut result);
573 result
574 }
575 fn collect_paths(group: &ShdfGroup, prefix: &str, out: &mut Vec<String>) {
576 let base = if prefix.is_empty() {
577 format!("/{}", group.name)
578 } else {
579 format!("{}/{}", prefix, group.name)
580 };
581 for ds in &group.datasets {
582 out.push(format!("{}/{}", base, ds.name));
583 }
584 for child in &group.children {
585 Self::collect_paths(child, &base, out);
586 }
587 }
588 pub fn total_datasets(&self) -> usize {
590 self.root.total_datasets()
591 }
592}
593#[derive(Debug, Clone)]
595#[allow(dead_code)]
596pub struct ChunkingConfig {
597 pub chunk_dims: Vec<usize>,
599}
600#[allow(dead_code)]
601impl ChunkingConfig {
602 pub fn new(chunk_dims: Vec<usize>) -> Self {
604 Self { chunk_dims }
605 }
606 pub fn n_chunks(&self, shape: &[usize]) -> usize {
608 if shape.len() != self.chunk_dims.len() {
609 return 0;
610 }
611 let mut total = 1_usize;
612 for (s, c) in shape.iter().zip(self.chunk_dims.iter()) {
613 if *c == 0 {
614 return 0;
615 }
616 total *= (*s).div_ceil(*c);
617 }
618 total
619 }
620 pub fn chunk_index(&self, element_idx: &[usize], shape: &[usize]) -> usize {
622 if shape.len() != self.chunk_dims.len() || element_idx.len() != shape.len() {
623 return 0;
624 }
625 let mut idx = 0;
626 let mut stride = 1;
627 for d in (0..shape.len()).rev() {
628 let chunk_pos = element_idx[d] / self.chunk_dims[d].max(1);
629 let n_chunks_d = (shape[d] + self.chunk_dims[d] - 1) / self.chunk_dims[d].max(1);
630 idx += chunk_pos * stride;
631 stride *= n_chunks_d;
632 }
633 idx
634 }
635 pub fn default_for_shape(shape: &[usize]) -> Self {
637 let chunk_dims: Vec<usize> = shape.iter().map(|&s| s.min(64)).collect();
638 Self { chunk_dims }
639 }
640}
641#[derive(Debug, Clone, PartialEq)]
643#[allow(dead_code)]
644pub enum AttributeValue {
645 String(String),
647 Float64(f64),
649 Int32(i32),
651 Bool(bool),
653}
654#[derive(Debug, Clone)]
656#[allow(dead_code)]
657pub struct ShdfGroup {
658 pub name: String,
660 pub datasets: Vec<Dataset>,
662 pub children: Vec<ShdfGroup>,
664 pub attributes: Vec<(String, String)>,
666}
667#[allow(dead_code)]
668impl ShdfGroup {
669 pub fn new(name: &str) -> Self {
671 Self {
672 name: name.to_string(),
673 datasets: Vec::new(),
674 children: Vec::new(),
675 attributes: Vec::new(),
676 }
677 }
678 pub fn add_dataset_f64(&mut self, name: &str, shape: Vec<usize>, data: Vec<f64>) {
680 self.datasets.push(Dataset {
681 name: name.to_string(),
682 shape,
683 dtype: DataType::Float64,
684 data_f64: data,
685 data_i32: Vec::new(),
686 attributes: Vec::new(),
687 });
688 }
689 pub fn add_dataset_i32(&mut self, name: &str, shape: Vec<usize>, data: Vec<i32>) {
691 self.datasets.push(Dataset {
692 name: name.to_string(),
693 shape,
694 dtype: DataType::Int32,
695 data_f64: Vec::new(),
696 data_i32: data,
697 attributes: Vec::new(),
698 });
699 }
700 pub fn add_child(&mut self, child: ShdfGroup) {
702 self.children.push(child);
703 }
704 pub fn add_attribute(&mut self, key: &str, value: &str) {
706 self.attributes.push((key.to_string(), value.to_string()));
707 }
708 pub fn get_dataset(&self, name: &str) -> Option<&Dataset> {
710 self.datasets.iter().find(|d| d.name == name)
711 }
712 pub fn get_child(&self, name: &str) -> Option<&ShdfGroup> {
714 self.children.iter().find(|c| c.name == name)
715 }
716 pub fn total_datasets(&self) -> usize {
718 self.datasets.len()
719 + self
720 .children
721 .iter()
722 .map(|c| c.total_datasets())
723 .sum::<usize>()
724 }
725 pub fn summary(&self, indent: usize) -> String {
727 let prefix = " ".repeat(indent);
728 let mut out = format!("{prefix}Group: {}\n", self.name);
729 for (k, v) in &self.attributes {
730 out.push_str(&format!("{prefix} attr: {k} = {v}\n"));
731 }
732 for ds in &self.datasets {
733 let shape_str: Vec<String> = ds.shape.iter().map(|s| s.to_string()).collect();
734 out.push_str(&format!(
735 "{prefix} Dataset: {} shape=[{}] dtype={:?}\n",
736 ds.name,
737 shape_str.join("x"),
738 ds.dtype,
739 ));
740 }
741 for child in &self.children {
742 out.push_str(&child.summary(indent + 2));
743 }
744 out
745 }
746}
747#[allow(dead_code)]
752pub struct TimeSeriesAppender {
753 pub name: String,
755 pub data: Vec<f64>,
757 pub n_frames: usize,
759 pub frame_width: usize,
761}
762impl TimeSeriesAppender {
763 pub fn new(name: impl Into<String>, frame_width: usize) -> Self {
767 Self {
768 name: name.into(),
769 data: Vec::new(),
770 n_frames: 0,
771 frame_width,
772 }
773 }
774 pub fn append(&mut self, frame: &[f64]) {
779 assert_eq!(
780 frame.len(),
781 self.frame_width,
782 "frame length {} != frame_width {}",
783 frame.len(),
784 self.frame_width
785 );
786 self.data.extend_from_slice(frame);
787 self.n_frames += 1;
788 }
789 pub fn total_samples(&self) -> usize {
791 self.data.len()
792 }
793 pub fn get_frame(&self, idx: usize) -> Option<&[f64]> {
795 let start = idx * self.frame_width;
796 let end = start + self.frame_width;
797 self.data.get(start..end)
798 }
799 pub fn to_dataset(&self) -> Dataset {
801 let mut ds = Dataset {
802 name: self.name.clone(),
803 dtype: DataType::Float64,
804 shape: vec![self.n_frames, self.frame_width],
805 data_f64: self.data.clone(),
806 data_i32: Vec::new(),
807 attributes: Vec::new(),
808 };
809 ds.attributes
810 .push(("n_frames".to_string(), self.n_frames.to_string()));
811 ds.attributes
812 .push(("frame_width".to_string(), self.frame_width.to_string()));
813 ds
814 }
815}
816#[allow(dead_code)]
819#[derive(Debug, Clone)]
820pub struct VirtualLink {
821 pub virtual_path: String,
823 pub source_file: String,
825 pub source_path: String,
827 pub slices: Vec<[usize; 3]>,
829}
830impl VirtualLink {
831 pub fn new(
833 virtual_path: impl Into<String>,
834 source_file: impl Into<String>,
835 source_path: impl Into<String>,
836 ) -> Self {
837 Self {
838 virtual_path: virtual_path.into(),
839 source_file: source_file.into(),
840 source_path: source_path.into(),
841 slices: Vec::new(),
842 }
843 }
844 pub fn with_slice(mut self, start: usize, stop: usize, step: usize) -> Self {
846 self.slices.push([start, stop, step]);
847 self
848 }
849 pub fn to_cdl(&self) -> String {
851 let slice_str = if self.slices.is_empty() {
852 "(:)".to_string()
853 } else {
854 let parts: Vec<String> = self
855 .slices
856 .iter()
857 .map(|s| format!("{}:{}:{}", s[0], s[1], s[2]))
858 .collect();
859 format!("({})", parts.join(", "))
860 };
861 format!(
862 "{} -> {}:{}{}",
863 self.virtual_path, self.source_file, self.source_path, slice_str
864 )
865 }
866}
867#[derive(Debug, Clone)]
869#[allow(dead_code)]
870pub struct ShdfFile {
871 pub datasets: Vec<Dataset>,
873 pub global_attributes: Vec<(String, String)>,
875}
876impl ShdfFile {
877 #[allow(dead_code)]
879 pub fn new() -> Self {
880 ShdfFile {
881 datasets: Vec::new(),
882 global_attributes: Vec::new(),
883 }
884 }
885 #[allow(dead_code)]
887 pub fn add_dataset_f64(&mut self, name: &str, shape: Vec<usize>, data: Vec<f64>) {
888 self.datasets.push(Dataset {
889 name: name.to_string(),
890 shape,
891 dtype: DataType::Float64,
892 data_f64: data,
893 data_i32: Vec::new(),
894 attributes: Vec::new(),
895 });
896 }
897 #[allow(dead_code)]
899 pub fn add_dataset_i32(&mut self, name: &str, shape: Vec<usize>, data: Vec<i32>) {
900 self.datasets.push(Dataset {
901 name: name.to_string(),
902 shape,
903 dtype: DataType::Int32,
904 data_f64: Vec::new(),
905 data_i32: data,
906 attributes: Vec::new(),
907 });
908 }
909 #[allow(dead_code)]
911 pub fn add_global_attr(&mut self, key: &str, value: &str) {
912 self.global_attributes
913 .push((key.to_string(), value.to_string()));
914 }
915 #[allow(dead_code)]
917 pub fn get_f64(&self, name: &str) -> Option<&[f64]> {
918 self.datasets
919 .iter()
920 .find(|d| d.name == name)
921 .map(|d| d.data_f64.as_slice())
922 }
923 #[allow(dead_code)]
925 pub fn get_i32(&self, name: &str) -> Option<&[i32]> {
926 self.datasets
927 .iter()
928 .find(|d| d.name == name)
929 .map(|d| d.data_i32.as_slice())
930 }
931 #[allow(dead_code)]
933 pub fn to_bytes(&self) -> Vec<u8> {
934 let mut out: Vec<u8> = Vec::new();
935 out.extend_from_slice(MAGIC);
936 out.extend_from_slice(&VERSION.to_le_bytes());
937 out.extend_from_slice(&(self.global_attributes.len() as u32).to_le_bytes());
938 for (k, v) in &self.global_attributes {
939 out.extend_from_slice(&encode_string(k));
940 out.extend_from_slice(&encode_string(v));
941 }
942 out.extend_from_slice(&(self.datasets.len() as u32).to_le_bytes());
943 for ds in &self.datasets {
944 out.extend_from_slice(&encode_string(&ds.name));
945 let dtype_byte: u8 = match ds.dtype {
946 DataType::Float64 => 0,
947 DataType::Float32 => 1,
948 DataType::Int32 => 2,
949 DataType::Int64 => 3,
950 };
951 out.push(dtype_byte);
952 out.extend_from_slice(&(ds.shape.len() as u32).to_le_bytes());
953 for &dim in &ds.shape {
954 out.extend_from_slice(&(dim as u64).to_le_bytes());
955 }
956 match ds.dtype {
957 DataType::Float64 => {
958 out.extend_from_slice(&(ds.data_f64.len() as u64).to_le_bytes());
959 for &v in &ds.data_f64 {
960 out.extend_from_slice(&v.to_le_bytes());
961 }
962 }
963 DataType::Float32 => {
964 out.extend_from_slice(&(ds.data_f64.len() as u64).to_le_bytes());
965 for &v in &ds.data_f64 {
966 out.extend_from_slice(&(v as f32).to_le_bytes());
967 }
968 }
969 DataType::Int32 => {
970 out.extend_from_slice(&(ds.data_i32.len() as u64).to_le_bytes());
971 for &v in &ds.data_i32 {
972 out.extend_from_slice(&v.to_le_bytes());
973 }
974 }
975 DataType::Int64 => {
976 out.extend_from_slice(&(ds.data_i32.len() as u64).to_le_bytes());
977 for &v in &ds.data_i32 {
978 out.extend_from_slice(&(v as i64).to_le_bytes());
979 }
980 }
981 }
982 out.extend_from_slice(&(ds.attributes.len() as u32).to_le_bytes());
983 for (k, v) in &ds.attributes {
984 out.extend_from_slice(&encode_string(k));
985 out.extend_from_slice(&encode_string(v));
986 }
987 }
988 out
989 }
990 #[allow(dead_code)]
994 pub fn from_bytes(data: &[u8]) -> Result<Self, String> {
995 let mut pos: usize = 0;
996 if data.len() < 4 {
997 return Err("too short for magic".to_string());
998 }
999 if &data[pos..pos + 4] != MAGIC {
1000 return Err(format!("bad magic: {:?}", &data[pos..pos + 4]));
1001 }
1002 pos += 4;
1003 let version = read_u32(data, &mut pos)?;
1004 if version != VERSION {
1005 return Err(format!("unsupported version: {version}"));
1006 }
1007 let n_global = read_u32(data, &mut pos)? as usize;
1008 let mut global_attributes = Vec::with_capacity(n_global);
1009 for _ in 0..n_global {
1010 let k = decode_string(data, &mut pos)?;
1011 let v = decode_string(data, &mut pos)?;
1012 global_attributes.push((k, v));
1013 }
1014 let n_datasets = read_u32(data, &mut pos)? as usize;
1015 let mut datasets = Vec::with_capacity(n_datasets);
1016 for _ in 0..n_datasets {
1017 let name = decode_string(data, &mut pos)?;
1018 let dtype_byte = read_u8(data, &mut pos)?;
1019 let dtype = match dtype_byte {
1020 0 => DataType::Float64,
1021 1 => DataType::Float32,
1022 2 => DataType::Int32,
1023 3 => DataType::Int64,
1024 _ => return Err(format!("unknown dtype byte: {dtype_byte}")),
1025 };
1026 let n_dims = read_u32(data, &mut pos)? as usize;
1027 let mut shape = Vec::with_capacity(n_dims);
1028 for _ in 0..n_dims {
1029 shape.push(read_u64(data, &mut pos)? as usize);
1030 }
1031 let n_elems = read_u64(data, &mut pos)? as usize;
1032 let mut data_f64 = Vec::new();
1033 let mut data_i32 = Vec::new();
1034 match dtype {
1035 DataType::Float64 => {
1036 data_f64.reserve(n_elems);
1037 for _ in 0..n_elems {
1038 data_f64.push(read_f64(data, &mut pos)?);
1039 }
1040 }
1041 DataType::Float32 => {
1042 data_f64.reserve(n_elems);
1043 for _ in 0..n_elems {
1044 data_f64.push(read_f32(data, &mut pos)? as f64);
1045 }
1046 }
1047 DataType::Int32 => {
1048 data_i32.reserve(n_elems);
1049 for _ in 0..n_elems {
1050 data_i32.push(read_i32(data, &mut pos)?);
1051 }
1052 }
1053 DataType::Int64 => {
1054 data_i32.reserve(n_elems);
1055 for _ in 0..n_elems {
1056 data_i32.push(read_i64(data, &mut pos)? as i32);
1057 }
1058 }
1059 }
1060 let n_attrs = read_u32(data, &mut pos)? as usize;
1061 let mut attributes = Vec::with_capacity(n_attrs);
1062 for _ in 0..n_attrs {
1063 let k = decode_string(data, &mut pos)?;
1064 let v = decode_string(data, &mut pos)?;
1065 attributes.push((k, v));
1066 }
1067 datasets.push(Dataset {
1068 name,
1069 shape,
1070 dtype,
1071 data_f64,
1072 data_i32,
1073 attributes,
1074 });
1075 }
1076 Ok(ShdfFile {
1077 datasets,
1078 global_attributes,
1079 })
1080 }
1081 #[allow(dead_code)]
1083 pub fn write_to_text(&self) -> String {
1084 let mut out = String::new();
1085 out.push_str("=== SHDF File Summary ===\n");
1086 if !self.global_attributes.is_empty() {
1087 out.push_str("Global attributes:\n");
1088 for (k, v) in &self.global_attributes {
1089 out.push_str(&format!(" {k} = {v}\n"));
1090 }
1091 }
1092 out.push_str(&format!("Datasets: {}\n", self.datasets.len()));
1093 for ds in &self.datasets {
1094 let shape_str: Vec<String> = ds.shape.iter().map(|s| s.to_string()).collect();
1095 out.push_str(&format!(
1096 " [{}] shape=[{}] dtype={:?}\n",
1097 ds.name,
1098 shape_str.join("×"),
1099 ds.dtype,
1100 ));
1101 let preview = match ds.dtype {
1102 DataType::Float64 | DataType::Float32 => {
1103 let vals: Vec<String> = ds
1104 .data_f64
1105 .iter()
1106 .take(5)
1107 .map(|v| format!("{v:.6}"))
1108 .collect();
1109 vals.join(", ")
1110 }
1111 DataType::Int32 | DataType::Int64 => {
1112 let vals: Vec<String> =
1113 ds.data_i32.iter().take(5).map(|v| v.to_string()).collect();
1114 vals.join(", ")
1115 }
1116 };
1117 if !preview.is_empty() {
1118 out.push_str(&format!(" first values: [{preview}]\n"));
1119 }
1120 if !ds.attributes.is_empty() {
1121 out.push_str(" attributes:\n");
1122 for (k, v) in &ds.attributes {
1123 out.push_str(&format!(" {k} = {v}\n"));
1124 }
1125 }
1126 }
1127 out
1128 }
1129}
1130impl Default for ShdfFile {
1131 fn default() -> Self {
1132 Self::new()
1133 }
1134}
1135#[allow(dead_code)]
1137pub struct AttributeHelper;
1138#[allow(dead_code)]
1139impl AttributeHelper {
1140 pub fn to_string(val: &AttributeValue) -> String {
1142 match val {
1143 AttributeValue::String(s) => format!("s:{s}"),
1144 AttributeValue::Float64(f) => format!("f:{f}"),
1145 AttributeValue::Int32(i) => format!("i:{i}"),
1146 AttributeValue::Bool(b) => format!("b:{b}"),
1147 }
1148 }
1149 pub fn from_string(s: &str) -> AttributeValue {
1151 if let Some(rest) = s.strip_prefix("f:")
1152 && let Ok(f) = rest.parse::<f64>()
1153 {
1154 return AttributeValue::Float64(f);
1155 }
1156 if let Some(rest) = s.strip_prefix("i:")
1157 && let Ok(i) = rest.parse::<i32>()
1158 {
1159 return AttributeValue::Int32(i);
1160 }
1161 if let Some(rest) = s.strip_prefix("b:") {
1162 return AttributeValue::Bool(rest == "true");
1163 }
1164 if let Some(rest) = s.strip_prefix("s:") {
1165 return AttributeValue::String(rest.to_string());
1166 }
1167 AttributeValue::String(s.to_string())
1168 }
1169}