Skip to main content

oxiphysics_io/hdf5_io/
types.rs

1// Copyright 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Core types for the HDF5 mock: error, dtype, storage, layout, attributes, links.
5
6#![allow(dead_code)]
7
8use std::collections::HashMap;
9
10// ---------------------------------------------------------------------------
11// 1.  Error type
12// ---------------------------------------------------------------------------
13
14/// Errors that can occur when operating on the mock HDF5 store.
15#[derive(Debug, Clone, PartialEq)]
16pub enum Hdf5Error {
17    /// A group, dataset or attribute was not found under the given path.
18    NotFound(String),
19    /// An item with the given name already exists.
20    AlreadyExists(String),
21    /// The requested hyperslab indices are out of range.
22    HyperslabOutOfRange {
23        /// The dimension index where the error occurred.
24        dim: usize,
25        /// The requested start offset.
26        start: usize,
27        /// The requested length.
28        count: usize,
29        /// The actual dimension size.
30        size: usize,
31    },
32    /// The shapes or types are incompatible for the attempted operation.
33    ShapeMismatch {
34        /// Expected shape.
35        expected: Vec<usize>,
36        /// Provided shape.
37        got: Vec<usize>,
38    },
39    /// The file is locked and cannot be written.
40    FileLocked,
41    /// A link target was not found.
42    LinkTargetNotFound(String),
43    /// Generic error carrying a human-readable message.
44    Generic(String),
45}
46
47impl std::fmt::Display for Hdf5Error {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match self {
50            Hdf5Error::NotFound(p) => write!(f, "HDF5: not found: {p}"),
51            Hdf5Error::AlreadyExists(p) => write!(f, "HDF5: already exists: {p}"),
52            Hdf5Error::HyperslabOutOfRange {
53                dim,
54                start,
55                count,
56                size,
57            } => write!(
58                f,
59                "HDF5: hyperslab out of range: dim={dim} start={start} count={count} size={size}"
60            ),
61            Hdf5Error::ShapeMismatch { expected, got } => {
62                write!(
63                    f,
64                    "HDF5: shape mismatch: expected {expected:?}, got {got:?}"
65                )
66            }
67            Hdf5Error::FileLocked => write!(f, "HDF5: file is locked"),
68            Hdf5Error::LinkTargetNotFound(t) => {
69                write!(f, "HDF5: link target not found: {t}")
70            }
71            Hdf5Error::Generic(msg) => write!(f, "HDF5: {msg}"),
72        }
73    }
74}
75
76/// Convenient alias for results from this module.
77pub type Hdf5Result<T> = Result<T, Hdf5Error>;
78
79// ---------------------------------------------------------------------------
80// 2.  Datatypes
81// ---------------------------------------------------------------------------
82
83/// Scalar element type of an HDF5 dataset.
84#[derive(Debug, Clone, PartialEq)]
85pub enum Hdf5Dtype {
86    /// 32-bit IEEE float.
87    Float32,
88    /// 64-bit IEEE float.
89    Float64,
90    /// 32-bit signed integer.
91    Int32,
92    /// 8-bit unsigned integer (byte).
93    Uint8,
94    /// Variable-length UTF-8 string.
95    VlenString,
96    /// Compound (struct-like) type described by a list of (name, dtype) fields.
97    Compound(Vec<(String, Hdf5Dtype)>),
98    /// A user-defined named type that aliases another dtype.
99    Named {
100        /// The user-supplied type name.
101        name: String,
102        /// The underlying base dtype.
103        base: Box<Hdf5Dtype>,
104    },
105}
106
107impl Hdf5Dtype {
108    /// Return the byte-size of a single element (0 for variable-length types).
109    pub fn element_size(&self) -> usize {
110        match self {
111            Hdf5Dtype::Float32 => 4,
112            Hdf5Dtype::Float64 => 8,
113            Hdf5Dtype::Int32 => 4,
114            Hdf5Dtype::Uint8 => 1,
115            Hdf5Dtype::VlenString => 0,
116            Hdf5Dtype::Compound(fields) => fields.iter().map(|(_, dt)| dt.element_size()).sum(),
117            Hdf5Dtype::Named { base, .. } => base.element_size(),
118        }
119    }
120}
121
122// ---------------------------------------------------------------------------
123// 3.  Storage layout helpers
124// ---------------------------------------------------------------------------
125
126/// Chunked storage descriptor for a dataset.
127#[derive(Debug, Clone)]
128pub struct ChunkLayout {
129    /// Chunk dimensions (same rank as dataset shape).
130    pub chunk_shape: Vec<usize>,
131    /// Gzip compression level (0 = no compression, 1-9).
132    pub gzip_level: u8,
133}
134
135impl ChunkLayout {
136    /// Create a new chunk descriptor.
137    pub fn new(chunk_shape: Vec<usize>, gzip_level: u8) -> Self {
138        Self {
139            chunk_shape,
140            gzip_level: gzip_level.min(9),
141        }
142    }
143
144    /// Return the number of elements per chunk.
145    pub fn chunk_volume(&self) -> usize {
146        self.chunk_shape.iter().product()
147    }
148}
149
150/// Hyperslab selection: a start offset + count (length) per dimension.
151#[derive(Debug, Clone)]
152pub struct Hyperslab {
153    /// Start index per dimension.
154    pub start: Vec<usize>,
155    /// Number of elements to select per dimension.
156    pub count: Vec<usize>,
157}
158
159impl Hyperslab {
160    /// Create a new hyperslab.
161    pub fn new(start: Vec<usize>, count: Vec<usize>) -> Self {
162        assert_eq!(
163            start.len(),
164            count.len(),
165            "Hyperslab: start and count must have equal rank"
166        );
167        Self { start, count }
168    }
169
170    /// Total number of elements in the selection.
171    pub fn volume(&self) -> usize {
172        self.count.iter().product()
173    }
174
175    /// Validate this selection against a dataset shape.
176    pub fn validate(&self, shape: &[usize]) -> Hdf5Result<()> {
177        if self.start.len() != shape.len() {
178            return Err(Hdf5Error::Generic(format!(
179                "Hyperslab rank {} != dataset rank {}",
180                self.start.len(),
181                shape.len()
182            )));
183        }
184        for (dim, (&s, (&c, &sz))) in self
185            .start
186            .iter()
187            .zip(self.count.iter().zip(shape.iter()))
188            .enumerate()
189        {
190            if s + c > sz {
191                return Err(Hdf5Error::HyperslabOutOfRange {
192                    dim,
193                    start: s,
194                    count: c,
195                    size: sz,
196                });
197            }
198        }
199        Ok(())
200    }
201}
202
203// ---------------------------------------------------------------------------
204// 4.  Attribute value
205// ---------------------------------------------------------------------------
206
207/// The value stored in an HDF5 attribute.
208#[derive(Debug, Clone, PartialEq)]
209pub enum AttrValue {
210    /// Scalar 64-bit float.
211    Float64(f64),
212    /// Scalar 32-bit float.
213    Float32(f32),
214    /// Scalar 32-bit integer.
215    Int32(i32),
216    /// UTF-8 string.
217    String(String),
218    /// Array of 64-bit floats.
219    ArrayF64(Vec<f64>),
220    /// Array of 32-bit floats.
221    ArrayF32(Vec<f32>),
222    /// Array of 32-bit integers.
223    ArrayI32(Vec<i32>),
224}
225
226// ---------------------------------------------------------------------------
227// 5.  Dataset storage
228// ---------------------------------------------------------------------------
229
230/// In-memory storage for a single dataset element type.
231#[derive(Debug, Clone)]
232pub enum DataStorage {
233    /// Float32 array.
234    Float32(Vec<f32>),
235    /// Float64 array.
236    Float64(Vec<f64>),
237    /// Int32 array.
238    Int32(Vec<i32>),
239    /// Uint8 array.
240    Uint8(Vec<u8>),
241    /// Variable-length strings (one per element).
242    VlenString(Vec<String>),
243    /// Compound: each element is a map from field-name to float64 value.
244    Compound(Vec<HashMap<String, f64>>),
245}
246
247impl DataStorage {
248    /// Return the number of elements stored.
249    pub fn len(&self) -> usize {
250        match self {
251            DataStorage::Float32(v) => v.len(),
252            DataStorage::Float64(v) => v.len(),
253            DataStorage::Int32(v) => v.len(),
254            DataStorage::Uint8(v) => v.len(),
255            DataStorage::VlenString(v) => v.len(),
256            DataStorage::Compound(v) => v.len(),
257        }
258    }
259
260    /// Return `true` if the storage is empty.
261    pub fn is_empty(&self) -> bool {
262        self.len() == 0
263    }
264}
265
266// ---------------------------------------------------------------------------
267// 6.  Link types
268// ---------------------------------------------------------------------------
269
270/// An HDF5 link (soft or hard) stored in a group.
271#[derive(Debug, Clone)]
272pub enum Hdf5Link {
273    /// Soft (symbolic) link: stores the target path as a string.
274    Soft(String),
275    /// Hard link: stores the target path (both point to same object).
276    Hard(String),
277}
278
279// ---------------------------------------------------------------------------
280// 7.  External dataset reference
281// ---------------------------------------------------------------------------
282
283/// A reference to a dataset living in an external file.
284#[derive(Debug, Clone)]
285pub struct ExternalRef {
286    /// Simulated external filename.
287    pub filename: String,
288    /// Path inside the external file.
289    pub dataset_path: String,
290    /// Byte offset (simulated, 64-bit for large-file support).
291    pub byte_offset: u64,
292}
293
294// ---------------------------------------------------------------------------
295// 8.  Dimension scale descriptor
296// ---------------------------------------------------------------------------
297
298/// A dimension-scale association following the HDF5 DimensionScales convention.
299#[derive(Debug, Clone)]
300pub struct DimScale {
301    /// Path of the dataset that serves as a scale.
302    pub scale_dataset: String,
303    /// Axis index this scale is attached to.
304    pub axis: usize,
305    /// Human-readable label for this axis.
306    pub label: String,
307}
308
309// ---------------------------------------------------------------------------
310// 9.  Collective I/O metadata
311// ---------------------------------------------------------------------------
312
313/// Mock metadata for a collective-I/O operation (Parallel HDF5 style).
314#[derive(Debug, Clone)]
315pub struct CollectiveIoMeta {
316    /// Number of MPI ranks participating.
317    pub n_ranks: usize,
318    /// Rank that initiated the write.
319    pub root_rank: usize,
320    /// Total bytes transferred across all ranks.
321    pub total_bytes: u64,
322    /// Simulated wall-clock time in seconds.
323    pub wall_time_s: f64,
324}
325
326// ---------------------------------------------------------------------------
327// File lock simulation
328// ---------------------------------------------------------------------------
329
330/// File-level lock state for exclusive write access simulation.
331#[derive(Debug, Clone, PartialEq)]
332pub enum LockState {
333    /// No lock held.
334    Unlocked,
335    /// An exclusive write lock is held by `owner_id`.
336    WriteLocked {
337        /// Simulated process/thread identifier.
338        owner_id: u64,
339    },
340    /// A shared read lock is held by `n_readers`.
341    ReadLocked {
342        /// Number of concurrent readers.
343        n_readers: usize,
344    },
345}
346
347// ---------------------------------------------------------------------------
348// Parallel HDF5 metadata
349// ---------------------------------------------------------------------------
350
351/// Simulated parallel HDF5 (PHDF5) metadata for a multi-rank write.
352#[derive(Debug, Clone)]
353pub struct ParallelHdf5Meta {
354    /// Total number of MPI ranks.
355    pub n_ranks: usize,
356    /// Per-rank byte counts.
357    pub rank_byte_counts: Vec<u64>,
358    /// Whether collective metadata writes are enabled.
359    pub collective_metadata: bool,
360    /// POSIX advisory lock held during the collective operation.
361    pub lock_held: bool,
362}
363
364impl ParallelHdf5Meta {
365    /// Create metadata for a `n_ranks`-rank job.
366    pub fn new(n_ranks: usize) -> Self {
367        Self {
368            n_ranks,
369            rank_byte_counts: vec![0; n_ranks],
370            collective_metadata: true,
371            lock_held: false,
372        }
373    }
374
375    /// Record the number of bytes written by rank `rank`.
376    pub fn record_rank_bytes(&mut self, rank: usize, bytes: u64) {
377        if rank < self.n_ranks {
378            self.rank_byte_counts[rank] = bytes;
379        }
380    }
381
382    /// Total bytes across all ranks.
383    pub fn total_bytes(&self) -> u64 {
384        self.rank_byte_counts.iter().sum()
385    }
386}