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}