1#![allow(clippy::needless_range_loop)]
2#![allow(dead_code)]
12#![allow(clippy::too_many_arguments)]
13
14use std::collections::HashMap;
15
16const BASE64_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
21
22pub fn base64_encode(data: &[u8]) -> String {
24 let mut result = String::new();
25 let mut i = 0;
26 while i + 2 < data.len() {
27 let b0 = data[i] as usize;
28 let b1 = data[i + 1] as usize;
29 let b2 = data[i + 2] as usize;
30 result.push(BASE64_CHARS[b0 >> 2] as char);
31 result.push(BASE64_CHARS[((b0 & 3) << 4) | (b1 >> 4)] as char);
32 result.push(BASE64_CHARS[((b1 & 0xf) << 2) | (b2 >> 6)] as char);
33 result.push(BASE64_CHARS[b2 & 0x3f] as char);
34 i += 3;
35 }
36 let remaining = data.len() - i;
37 if remaining == 1 {
38 let b0 = data[i] as usize;
39 result.push(BASE64_CHARS[b0 >> 2] as char);
40 result.push(BASE64_CHARS[(b0 & 3) << 4] as char);
41 result.push('=');
42 result.push('=');
43 } else if remaining == 2 {
44 let b0 = data[i] as usize;
45 let b1 = data[i + 1] as usize;
46 result.push(BASE64_CHARS[b0 >> 2] as char);
47 result.push(BASE64_CHARS[((b0 & 3) << 4) | (b1 >> 4)] as char);
48 result.push(BASE64_CHARS[(b1 & 0xf) << 2] as char);
49 result.push('=');
50 }
51 result
52}
53
54pub fn f64_slice_to_base64(data: &[f64]) -> String {
56 let bytes: Vec<u8> = data.iter().flat_map(|v| v.to_le_bytes().to_vec()).collect();
57 base64_encode(&bytes)
58}
59
60#[derive(Clone, Debug, PartialEq)]
66pub enum H5Dtype {
67 Float32,
69 Float64,
71 Int32,
73 Int64,
75 Uint8,
77}
78
79impl H5Dtype {
80 pub fn byte_size(&self) -> usize {
82 match self {
83 H5Dtype::Float32 => 4,
84 H5Dtype::Float64 => 8,
85 H5Dtype::Int32 => 4,
86 H5Dtype::Int64 => 8,
87 H5Dtype::Uint8 => 1,
88 }
89 }
90}
91
92#[derive(Clone, Debug)]
94pub struct Hdf5Dataset {
95 pub name: String,
97 pub dims: Vec<usize>,
99 pub dtype: H5Dtype,
101 pub chunk_size: Option<Vec<usize>>,
103 pub compression_level: u8,
105 pub attributes: HashMap<String, String>,
107 pub data: Vec<f64>,
109}
110
111impl Hdf5Dataset {
112 pub fn new(name: &str, dims: Vec<usize>, dtype: H5Dtype) -> Self {
114 let n: usize = dims.iter().product();
115 Self {
116 name: name.to_string(),
117 dims,
118 dtype,
119 chunk_size: None,
120 compression_level: 0,
121 attributes: HashMap::new(),
122 data: vec![0.0; n],
123 }
124 }
125
126 pub fn set_chunk_size(&mut self, chunk: Vec<usize>) {
128 self.chunk_size = Some(chunk);
129 }
130
131 pub fn set_compression(&mut self, level: u8) {
133 self.compression_level = level.min(9);
134 }
135
136 pub fn set_attr(&mut self, key: &str, value: &str) {
138 self.attributes.insert(key.to_string(), value.to_string());
139 }
140
141 pub fn get_attr(&self, key: &str) -> Option<&str> {
143 self.attributes.get(key).map(|s| s.as_str())
144 }
145
146 pub fn n_elements(&self) -> usize {
148 self.dims.iter().product()
149 }
150
151 pub fn memory_bytes(&self) -> usize {
153 self.n_elements() * self.dtype.byte_size()
154 }
155
156 pub fn write_slice(&mut self, offset: usize, values: &[f64]) {
158 let end = (offset + values.len()).min(self.data.len());
159 for (i, &v) in values.iter().enumerate() {
160 if offset + i < end {
161 self.data[offset + i] = v;
162 }
163 }
164 }
165
166 pub fn read_slice(&self, offset: usize, length: usize) -> Vec<f64> {
168 let end = (offset + length).min(self.data.len());
169 self.data[offset..end].to_vec()
170 }
171}
172
173#[derive(Clone, Debug)]
179pub struct Hdf5Group {
180 pub name: String,
182 pub children: Vec<String>,
184 pub attributes: HashMap<String, String>,
186 pub datasets: HashMap<String, Hdf5Dataset>,
188}
189
190impl Hdf5Group {
191 pub fn new(name: &str) -> Self {
193 Self {
194 name: name.to_string(),
195 children: Vec::new(),
196 attributes: HashMap::new(),
197 datasets: HashMap::new(),
198 }
199 }
200
201 pub fn add_child(&mut self, child_name: &str) {
203 self.children.push(child_name.to_string());
204 }
205
206 pub fn set_attr(&mut self, key: &str, value: &str) {
208 self.attributes.insert(key.to_string(), value.to_string());
209 }
210
211 pub fn create_dataset(&mut self, name: &str, dims: Vec<usize>, dtype: H5Dtype) {
213 let ds = Hdf5Dataset::new(name, dims, dtype);
214 self.datasets.insert(name.to_string(), ds);
215 }
216
217 pub fn get_dataset(&self, name: &str) -> Option<&Hdf5Dataset> {
219 self.datasets.get(name)
220 }
221
222 pub fn get_dataset_mut(&mut self, name: &str) -> Option<&mut Hdf5Dataset> {
224 self.datasets.get_mut(name)
225 }
226
227 pub fn dataset_names(&self) -> Vec<&str> {
229 self.datasets.keys().map(|s| s.as_str()).collect()
230 }
231}
232
233#[derive(Clone, Debug)]
239pub struct Hdf5File {
240 pub path: String,
242 pub root: Hdf5Group,
244 pub is_open: bool,
246 pub read_only: bool,
248}
249
250impl Hdf5File {
251 pub fn open(path: &str) -> Self {
253 Self {
254 path: path.to_string(),
255 root: Hdf5Group::new("/"),
256 is_open: true,
257 read_only: true,
258 }
259 }
260
261 pub fn create(path: &str) -> Self {
263 Self {
264 path: path.to_string(),
265 root: Hdf5Group::new("/"),
266 is_open: true,
267 read_only: false,
268 }
269 }
270
271 pub fn close(&mut self) {
273 self.is_open = false;
274 }
275
276 pub fn flush(&self) {}
278
279 pub fn create_group(&mut self, name: &str) -> &mut Hdf5Group {
281 self.root.add_child(name);
282 if !self.root.children.contains(&name.to_string()) {
284 self.root.children.push(name.to_string());
285 }
286 &mut self.root
287 }
288
289 pub fn root_group(&self) -> &Hdf5Group {
291 &self.root
292 }
293
294 pub fn root_group_mut(&mut self) -> &mut Hdf5Group {
296 &mut self.root
297 }
298
299 pub fn create_dataset(&mut self, name: &str, dims: Vec<usize>, dtype: H5Dtype) {
301 self.root.create_dataset(name, dims, dtype);
302 }
303}
304
305#[derive(Clone, Debug)]
311pub struct ParallelNetcdf {
312 pub path: String,
314 pub global_dims: Vec<usize>,
316 pub n_ranks: usize,
318 pub rank: usize,
320 pub variables: HashMap<String, Vec<f64>>,
322 pub var_attrs: HashMap<String, HashMap<String, String>>,
324}
325
326impl ParallelNetcdf {
327 pub fn new(path: &str, global_dims: Vec<usize>, n_ranks: usize, rank: usize) -> Self {
329 Self {
330 path: path.to_string(),
331 global_dims,
332 n_ranks,
333 rank,
334 variables: HashMap::new(),
335 var_attrs: HashMap::new(),
336 }
337 }
338
339 pub fn def_var(&mut self, name: &str, _dims: &[&str]) {
341 self.variables.insert(name.to_string(), Vec::new());
342 self.var_attrs.insert(name.to_string(), HashMap::new());
343 }
344
345 pub fn put_var(&mut self, name: &str, data: Vec<f64>) {
347 self.variables.insert(name.to_string(), data);
348 }
349
350 pub fn get_var(&self, name: &str) -> Option<&[f64]> {
352 self.variables.get(name).map(|v| v.as_slice())
353 }
354
355 pub fn set_var_attr(&mut self, var: &str, key: &str, value: &str) {
357 if let Some(attrs) = self.var_attrs.get_mut(var) {
358 attrs.insert(key.to_string(), value.to_string());
359 }
360 }
361
362 pub fn global_size(&self) -> usize {
364 self.global_dims.iter().product()
365 }
366
367 pub fn local_size(&self) -> usize {
369 if self.global_dims.is_empty() {
370 return 0;
371 }
372 let n_global_first = self.global_dims[0];
373 let local_first = n_global_first.div_ceil(self.n_ranks);
374 let rest: usize = self.global_dims[1..].iter().product::<usize>().max(1);
375 local_first.min(n_global_first) * rest
376 }
377}
378
379#[derive(Clone, Debug)]
385pub struct AdiosWriter {
386 pub stream_name: String,
388 pub is_open: bool,
390 pub staged_vars: HashMap<String, Vec<f64>>,
392 pub var_meta: HashMap<String, (Vec<usize>, String)>,
394 pub steps_written: usize,
396}
397
398impl AdiosWriter {
399 pub fn open(stream_name: &str) -> Self {
401 Self {
402 stream_name: stream_name.to_string(),
403 is_open: true,
404 staged_vars: HashMap::new(),
405 var_meta: HashMap::new(),
406 steps_written: 0,
407 }
408 }
409
410 pub fn define_variable(&mut self, name: &str, shape: Vec<usize>, dtype: &str) {
412 self.var_meta
413 .insert(name.to_string(), (shape, dtype.to_string()));
414 }
415
416 pub fn put_variable(&mut self, name: &str, data: Vec<f64>) {
418 self.staged_vars.insert(name.to_string(), data);
419 }
420
421 pub fn perform_puts(&mut self) {
423 self.steps_written += 1;
425 self.staged_vars.clear();
426 }
427
428 pub fn close(&mut self) {
430 self.is_open = false;
431 }
432
433 pub fn has_variable(&self, name: &str) -> bool {
435 self.var_meta.contains_key(name)
436 }
437
438 pub fn variable_shape(&self, name: &str) -> Option<&[usize]> {
440 self.var_meta.get(name).map(|(s, _)| s.as_slice())
441 }
442}
443
444#[derive(Clone, Debug)]
450pub struct CheckpointEntry {
451 pub index: usize,
453 pub step: u64,
455 pub path: String,
457 pub timestamp: f64,
459}
460
461#[derive(Clone, Debug)]
463pub struct CheckpointManager {
464 pub base_dir: String,
466 pub keep_last_n: usize,
468 pub entries: Vec<CheckpointEntry>,
470 pub next_index: usize,
472}
473
474impl CheckpointManager {
475 pub fn new(base_dir: &str, keep_last_n: usize) -> Self {
477 Self {
478 base_dir: base_dir.to_string(),
479 keep_last_n,
480 entries: Vec::new(),
481 next_index: 0,
482 }
483 }
484
485 pub fn register(&mut self, step: u64, timestamp: f64) -> String {
487 let path = format!("{}/checkpoint_{:06}.bin", self.base_dir, self.next_index);
488 let entry = CheckpointEntry {
489 index: self.next_index,
490 step,
491 path: path.clone(),
492 timestamp,
493 };
494 self.entries.push(entry);
495 self.next_index += 1;
496
497 while self.entries.len() > self.keep_last_n {
499 self.entries.remove(0);
500 }
501
502 path
503 }
504
505 pub fn latest(&self) -> Option<&CheckpointEntry> {
507 self.entries.last()
508 }
509
510 pub fn restore_by_index(&self, index: usize) -> Option<&CheckpointEntry> {
512 self.entries.iter().find(|e| e.index == index)
513 }
514
515 pub fn n_checkpoints(&self) -> usize {
517 self.entries.len()
518 }
519}
520
521#[derive(Clone, Debug)]
527pub struct RestartFile {
528 pub path: String,
530 pub state: Vec<f64>,
532 pub step: u64,
534 pub time: f64,
536 pub metadata: HashMap<String, f64>,
538}
539
540impl RestartFile {
541 pub fn new(path: &str) -> Self {
543 Self {
544 path: path.to_string(),
545 state: Vec::new(),
546 step: 0,
547 time: 0.0,
548 metadata: HashMap::new(),
549 }
550 }
551
552 pub fn write_to_buffer(&self) -> Vec<u8> {
554 let mut buf = Vec::new();
555 buf.extend_from_slice(b"OXIRS001");
557 buf.extend_from_slice(&self.step.to_le_bytes());
559 buf.extend_from_slice(&self.time.to_le_bytes());
561 let n = self.state.len() as u64;
563 buf.extend_from_slice(&n.to_le_bytes());
564 for &v in &self.state {
566 buf.extend_from_slice(&v.to_le_bytes());
567 }
568 buf
569 }
570
571 pub fn read_from_buffer(path: &str, buf: &[u8]) -> Option<Self> {
573 if buf.len() < 24 || &buf[..8] != b"OXIRS001" {
574 return None;
575 }
576 let step = u64::from_le_bytes(buf[8..16].try_into().ok()?);
577 let time = f64::from_le_bytes(buf[16..24].try_into().ok()?);
578 let n = u64::from_le_bytes(buf[24..32].try_into().ok()?) as usize;
579 let mut state = Vec::with_capacity(n);
580 for i in 0..n {
581 let off = 32 + i * 8;
582 if off + 8 > buf.len() {
583 break;
584 }
585 let v = f64::from_le_bytes(buf[off..off + 8].try_into().ok()?);
586 state.push(v);
587 }
588 Some(Self {
589 path: path.to_string(),
590 state,
591 step,
592 time,
593 metadata: HashMap::new(),
594 })
595 }
596
597 pub fn set_meta(&mut self, key: &str, value: f64) {
599 self.metadata.insert(key.to_string(), value);
600 }
601}
602
603#[derive(Clone, Debug)]
609pub struct DistributedMeshIO {
610 pub rank: usize,
612 pub n_ranks: usize,
614 pub local_nodes: Vec<[f64; 3]>,
616 pub ghost_nodes: Vec<[f64; 3]>,
618 pub local_global_ids: Vec<u64>,
620 pub ghost_global_ids: Vec<u64>,
622 pub elements: Vec<Vec<usize>>,
624}
625
626impl DistributedMeshIO {
627 pub fn new(rank: usize, n_ranks: usize) -> Self {
629 Self {
630 rank,
631 n_ranks,
632 local_nodes: Vec::new(),
633 ghost_nodes: Vec::new(),
634 local_global_ids: Vec::new(),
635 ghost_global_ids: Vec::new(),
636 elements: Vec::new(),
637 }
638 }
639
640 pub fn add_local_node(&mut self, pos: [f64; 3], global_id: u64) {
642 self.local_nodes.push(pos);
643 self.local_global_ids.push(global_id);
644 }
645
646 pub fn add_ghost_node(&mut self, pos: [f64; 3], global_id: u64) {
648 self.ghost_nodes.push(pos);
649 self.ghost_global_ids.push(global_id);
650 }
651
652 pub fn add_element(&mut self, nodes: Vec<usize>) {
654 self.elements.push(nodes);
655 }
656
657 pub fn total_nodes(&self) -> usize {
659 self.local_nodes.len() + self.ghost_nodes.len()
660 }
661
662 pub fn serialize(&self) -> Vec<u8> {
664 let mut buf = Vec::new();
665 buf.extend_from_slice(b"OXIDMESH");
666 let n_local = self.local_nodes.len() as u64;
667 buf.extend_from_slice(&n_local.to_le_bytes());
668 for node in &self.local_nodes {
669 for &c in node {
670 buf.extend_from_slice(&c.to_le_bytes());
671 }
672 }
673 buf
674 }
675
676 pub fn partition_regular_grid(
678 n_nodes_total: usize,
679 positions: &[[f64; 3]],
680 rank: usize,
681 n_ranks: usize,
682 ) -> Self {
683 let block = n_nodes_total.div_ceil(n_ranks);
684 let start = rank * block;
685 let end = (start + block).min(n_nodes_total);
686 let mut mesh = Self::new(rank, n_ranks);
687 for i in start..end {
688 mesh.add_local_node(positions[i], i as u64);
689 }
690 mesh
691 }
692}
693
694#[derive(Clone, Debug)]
700pub struct PerfEntry {
701 pub crate_name: String,
703 pub function: String,
705 pub time_ms: f64,
707 pub memory_mb: f64,
709 pub n_threads: usize,
711 pub notes: String,
713}
714
715impl PerfEntry {
716 pub fn new(
718 crate_name: &str,
719 function: &str,
720 time_ms: f64,
721 memory_mb: f64,
722 n_threads: usize,
723 ) -> Self {
724 Self {
725 crate_name: crate_name.to_string(),
726 function: function.to_string(),
727 time_ms,
728 memory_mb,
729 n_threads,
730 notes: String::new(),
731 }
732 }
733}
734
735#[derive(Clone, Debug)]
737pub struct PerformanceLog {
738 pub entries: Vec<PerfEntry>,
740 pub run_id: String,
742}
743
744impl PerformanceLog {
745 pub fn new(run_id: &str) -> Self {
747 Self {
748 entries: Vec::new(),
749 run_id: run_id.to_string(),
750 }
751 }
752
753 pub fn record(&mut self, entry: PerfEntry) {
755 self.entries.push(entry);
756 }
757
758 pub fn total_time_ms(&self) -> f64 {
760 self.entries.iter().map(|e| e.time_ms).sum()
761 }
762
763 pub fn mean_time_ms(&self) -> f64 {
765 if self.entries.is_empty() {
766 return 0.0;
767 }
768 self.total_time_ms() / self.entries.len() as f64
769 }
770
771 pub fn peak_memory_mb(&self) -> f64 {
773 self.entries
774 .iter()
775 .map(|e| e.memory_mb)
776 .fold(0.0f64, f64::max)
777 }
778
779 pub fn filter_by_crate(&self, crate_name: &str) -> Vec<&PerfEntry> {
781 self.entries
782 .iter()
783 .filter(|e| e.crate_name == crate_name)
784 .collect()
785 }
786
787 pub fn to_csv(&self) -> String {
789 let mut s = "crate,function,time_ms,memory_mb,n_threads\n".to_string();
790 for e in &self.entries {
791 s.push_str(&format!(
792 "{},{},{:.3},{:.3},{}\n",
793 e.crate_name, e.function, e.time_ms, e.memory_mb, e.n_threads
794 ));
795 }
796 s
797 }
798
799 pub fn to_json(&self) -> String {
801 let entries_json: Vec<String> = self
802 .entries
803 .iter()
804 .map(|e| {
805 format!(
806 r#"{{"crate":"{}","function":"{}","time_ms":{:.3},"memory_mb":{:.3},"n_threads":{}}}"#,
807 e.crate_name, e.function, e.time_ms, e.memory_mb, e.n_threads
808 )
809 })
810 .collect();
811 format!(
812 r#"{{"run_id":"{}","entries":[{}]}}"#,
813 self.run_id,
814 entries_json.join(",")
815 )
816 }
817}
818
819#[derive(Clone, Debug)]
825pub struct ScientificJson {
826 pub name: String,
828 pub metadata: HashMap<String, String>,
830 pub arrays: HashMap<String, Vec<f64>>,
832 pub scalars: HashMap<String, f64>,
834}
835
836impl ScientificJson {
837 pub fn new(name: &str) -> Self {
839 Self {
840 name: name.to_string(),
841 metadata: HashMap::new(),
842 arrays: HashMap::new(),
843 scalars: HashMap::new(),
844 }
845 }
846
847 pub fn set_meta(&mut self, key: &str, value: &str) {
849 self.metadata.insert(key.to_string(), value.to_string());
850 }
851
852 pub fn set_scalar(&mut self, key: &str, value: f64) {
854 self.scalars.insert(key.to_string(), value);
855 }
856
857 pub fn add_array(&mut self, name: &str, data: Vec<f64>) {
859 self.arrays.insert(name.to_string(), data);
860 }
861
862 pub fn get_array(&self, name: &str) -> Option<&[f64]> {
864 self.arrays.get(name).map(|v| v.as_slice())
865 }
866
867 pub fn to_json(&self) -> String {
869 let mut parts: Vec<String> = Vec::new();
870
871 let meta_parts: Vec<String> = self
873 .metadata
874 .iter()
875 .map(|(k, v)| format!(r#""{}":"{}""#, k, v))
876 .collect();
877 if !meta_parts.is_empty() {
878 parts.push(format!(r#""metadata":{{{}}}"#, meta_parts.join(",")));
879 }
880
881 let scalar_parts: Vec<String> = self
883 .scalars
884 .iter()
885 .map(|(k, v)| format!(r#""{}":{}"#, k, v))
886 .collect();
887 if !scalar_parts.is_empty() {
888 parts.push(format!(r#""scalars":{{{}}}"#, scalar_parts.join(",")));
889 }
890
891 let arr_parts: Vec<String> = self
893 .arrays
894 .iter()
895 .map(|(k, v)| {
896 let b64 = f64_slice_to_base64(v);
897 format!(
898 r#""{}":{{"dtype":"f64","shape":[{}],"base64":"{}"}}"#,
899 k,
900 v.len(),
901 b64
902 )
903 })
904 .collect();
905 if !arr_parts.is_empty() {
906 parts.push(format!(r#""arrays":{{{}}}"#, arr_parts.join(",")));
907 }
908
909 format!(r#"{{"name":"{}",{}}}"#, self.name, parts.join(","))
910 }
911
912 pub fn parse_scalar(json: &str, key: &str) -> Option<f64> {
914 let search = format!("\"{}\":", key);
915 let start = json.find(&search)? + search.len();
916 let rest = &json[start..];
917 let end = rest.find([',', '}']).unwrap_or(rest.len());
918 rest[..end].trim().parse().ok()
919 }
920}
921
922#[cfg(test)]
927mod tests {
928 use super::*;
929
930 #[test]
931 fn test_base64_encode_empty() {
932 assert_eq!(base64_encode(&[]), "");
933 }
934
935 #[test]
936 fn test_base64_encode_hello() {
937 let encoded = base64_encode(b"Hello");
938 assert_eq!(encoded, "SGVsbG8=");
939 }
940
941 #[test]
942 fn test_f64_slice_to_base64_roundtrip_length() {
943 let data = vec![1.0, 2.0, 3.0];
944 let b64 = f64_slice_to_base64(&data);
945 assert_eq!(b64.len(), 32);
947 }
948
949 #[test]
950 fn test_h5dtype_byte_size() {
951 assert_eq!(H5Dtype::Float32.byte_size(), 4);
952 assert_eq!(H5Dtype::Float64.byte_size(), 8);
953 assert_eq!(H5Dtype::Uint8.byte_size(), 1);
954 }
955
956 #[test]
957 fn test_hdf5_dataset_new() {
958 let ds = Hdf5Dataset::new("data", vec![10, 10], H5Dtype::Float64);
959 assert_eq!(ds.n_elements(), 100);
960 assert_eq!(ds.data.len(), 100);
961 }
962
963 #[test]
964 fn test_hdf5_dataset_memory_bytes() {
965 let ds = Hdf5Dataset::new("data", vec![10], H5Dtype::Float64);
966 assert_eq!(ds.memory_bytes(), 80);
967 }
968
969 #[test]
970 fn test_hdf5_dataset_write_read_slice() {
971 let mut ds = Hdf5Dataset::new("data", vec![10], H5Dtype::Float64);
972 ds.write_slice(2, &[1.0, 2.0, 3.0]);
973 let r = ds.read_slice(2, 3);
974 assert_eq!(r, vec![1.0, 2.0, 3.0]);
975 }
976
977 #[test]
978 fn test_hdf5_dataset_set_attr() {
979 let mut ds = Hdf5Dataset::new("data", vec![5], H5Dtype::Float32);
980 ds.set_attr("units", "m/s");
981 assert_eq!(ds.get_attr("units"), Some("m/s"));
982 }
983
984 #[test]
985 fn test_hdf5_dataset_compression() {
986 let mut ds = Hdf5Dataset::new("data", vec![5], H5Dtype::Float32);
987 ds.set_compression(15);
988 assert_eq!(ds.compression_level, 9); }
990
991 #[test]
992 fn test_hdf5_group_new() {
993 let g = Hdf5Group::new("sim");
994 assert_eq!(g.name, "sim");
995 assert!(g.datasets.is_empty());
996 }
997
998 #[test]
999 fn test_hdf5_group_create_dataset() {
1000 let mut g = Hdf5Group::new("results");
1001 g.create_dataset("velocity", vec![100, 3], H5Dtype::Float64);
1002 assert!(g.get_dataset("velocity").is_some());
1003 }
1004
1005 #[test]
1006 fn test_hdf5_group_dataset_names() {
1007 let mut g = Hdf5Group::new("results");
1008 g.create_dataset("pressure", vec![100], H5Dtype::Float64);
1009 g.create_dataset("density", vec![100], H5Dtype::Float64);
1010 let names = g.dataset_names();
1011 assert_eq!(names.len(), 2);
1012 }
1013
1014 #[test]
1015 fn test_hdf5_file_create() {
1016 let f = Hdf5File::create("test.h5");
1017 assert!(f.is_open);
1018 assert!(!f.read_only);
1019 }
1020
1021 #[test]
1022 fn test_hdf5_file_open() {
1023 let f = Hdf5File::open("test.h5");
1024 assert!(f.is_open);
1025 assert!(f.read_only);
1026 }
1027
1028 #[test]
1029 fn test_hdf5_file_create_dataset() {
1030 let mut f = Hdf5File::create("test.h5");
1031 f.create_dataset("temperatures", vec![50], H5Dtype::Float32);
1032 assert!(f.root.get_dataset("temperatures").is_some());
1033 }
1034
1035 #[test]
1036 fn test_hdf5_file_close() {
1037 let mut f = Hdf5File::create("test.h5");
1038 f.close();
1039 assert!(!f.is_open);
1040 }
1041
1042 #[test]
1043 fn test_parallel_netcdf_new() {
1044 let nc = ParallelNetcdf::new("out.nc", vec![100, 100, 100], 4, 0);
1045 assert_eq!(nc.global_size(), 1_000_000);
1046 }
1047
1048 #[test]
1049 fn test_parallel_netcdf_put_get_var() {
1050 let mut nc = ParallelNetcdf::new("out.nc", vec![10], 1, 0);
1051 nc.def_var("temp", &["x"]);
1052 nc.put_var("temp", vec![1.0, 2.0, 3.0]);
1053 let v = nc.get_var("temp").unwrap();
1054 assert_eq!(v, &[1.0, 2.0, 3.0]);
1055 }
1056
1057 #[test]
1058 fn test_parallel_netcdf_local_size() {
1059 let nc = ParallelNetcdf::new("out.nc", vec![100, 10], 4, 0);
1060 let ls = nc.local_size();
1061 assert!(ls > 0);
1062 }
1063
1064 #[test]
1065 fn test_adios_writer_open() {
1066 let w = AdiosWriter::open("sim.bp");
1067 assert!(w.is_open);
1068 assert_eq!(w.steps_written, 0);
1069 }
1070
1071 #[test]
1072 fn test_adios_writer_define_and_put() {
1073 let mut w = AdiosWriter::open("sim.bp");
1074 w.define_variable("velocity", vec![100, 3], "f64");
1075 assert!(w.has_variable("velocity"));
1076 w.put_variable("velocity", vec![1.0; 300]);
1077 assert!(w.staged_vars.contains_key("velocity"));
1078 }
1079
1080 #[test]
1081 fn test_adios_writer_perform_puts() {
1082 let mut w = AdiosWriter::open("sim.bp");
1083 w.put_variable("p", vec![1.0]);
1084 w.perform_puts();
1085 assert_eq!(w.steps_written, 1);
1086 assert!(w.staged_vars.is_empty());
1087 }
1088
1089 #[test]
1090 fn test_adios_writer_close() {
1091 let mut w = AdiosWriter::open("sim.bp");
1092 w.close();
1093 assert!(!w.is_open);
1094 }
1095
1096 #[test]
1097 fn test_checkpoint_manager_register() {
1098 let mut cm = CheckpointManager::new("/tmp/ckpt", 3);
1099 let path = cm.register(100, 1.0);
1100 assert!(path.contains("checkpoint_000000"));
1101 assert_eq!(cm.n_checkpoints(), 1);
1102 }
1103
1104 #[test]
1105 fn test_checkpoint_manager_keep_last_n() {
1106 let mut cm = CheckpointManager::new("/tmp/ckpt", 2);
1107 cm.register(0, 0.0);
1108 cm.register(1, 1.0);
1109 cm.register(2, 2.0);
1110 assert_eq!(cm.n_checkpoints(), 2);
1111 }
1112
1113 #[test]
1114 fn test_checkpoint_manager_latest() {
1115 let mut cm = CheckpointManager::new("/tmp/ckpt", 5);
1116 cm.register(10, 1.0);
1117 cm.register(20, 2.0);
1118 assert_eq!(cm.latest().unwrap().step, 20);
1119 }
1120
1121 #[test]
1122 fn test_checkpoint_manager_restore_by_index() {
1123 let mut cm = CheckpointManager::new("/tmp/ckpt", 5);
1124 cm.register(10, 1.0);
1125 let e = cm.restore_by_index(0);
1126 assert!(e.is_some());
1127 assert_eq!(e.unwrap().step, 10);
1128 }
1129
1130 #[test]
1131 fn test_restart_file_write_read() {
1132 let mut rf = RestartFile::new("restart.bin");
1133 rf.state = vec![1.0, 2.0, 3.0];
1134 rf.step = 500;
1135 rf.time = 0.5;
1136 let buf = rf.write_to_buffer();
1137 let rf2 = RestartFile::read_from_buffer("restart.bin", &buf).unwrap();
1138 assert_eq!(rf2.step, 500);
1139 assert!((rf2.time - 0.5).abs() < 1e-10);
1140 assert_eq!(rf2.state, vec![1.0, 2.0, 3.0]);
1141 }
1142
1143 #[test]
1144 fn test_restart_file_invalid_magic() {
1145 let buf = vec![0u8; 64];
1146 let rf = RestartFile::read_from_buffer("x.bin", &buf);
1147 assert!(rf.is_none());
1148 }
1149
1150 #[test]
1151 fn test_distributed_mesh_io_new() {
1152 let m = DistributedMeshIO::new(0, 4);
1153 assert_eq!(m.rank, 0);
1154 assert_eq!(m.total_nodes(), 0);
1155 }
1156
1157 #[test]
1158 fn test_distributed_mesh_io_add_nodes() {
1159 let mut m = DistributedMeshIO::new(0, 4);
1160 m.add_local_node([0.0, 0.0, 0.0], 0);
1161 m.add_ghost_node([1.0, 0.0, 0.0], 100);
1162 assert_eq!(m.total_nodes(), 2);
1163 assert_eq!(m.local_nodes.len(), 1);
1164 }
1165
1166 #[test]
1167 fn test_distributed_mesh_io_serialize() {
1168 let mut m = DistributedMeshIO::new(0, 1);
1169 m.add_local_node([1.0, 2.0, 3.0], 0);
1170 let buf = m.serialize();
1171 assert!(buf.starts_with(b"OXIDMESH"));
1172 }
1173
1174 #[test]
1175 fn test_distributed_mesh_io_partition() {
1176 let positions: Vec<[f64; 3]> = (0..10).map(|i| [i as f64, 0.0, 0.0]).collect();
1177 let m = DistributedMeshIO::partition_regular_grid(10, &positions, 0, 2);
1178 assert!(m.local_nodes.len() >= 4);
1179 }
1180
1181 #[test]
1182 fn test_performance_log_record() {
1183 let mut log = PerformanceLog::new("run001");
1184 log.record(PerfEntry::new("lbm", "stream", 10.0, 100.0, 4));
1185 assert_eq!(log.entries.len(), 1);
1186 }
1187
1188 #[test]
1189 fn test_performance_log_total_time() {
1190 let mut log = PerformanceLog::new("run001");
1191 log.record(PerfEntry::new("lbm", "collide", 5.0, 50.0, 4));
1192 log.record(PerfEntry::new("lbm", "stream", 3.0, 50.0, 4));
1193 assert!((log.total_time_ms() - 8.0).abs() < 1e-10);
1194 }
1195
1196 #[test]
1197 fn test_performance_log_peak_memory() {
1198 let mut log = PerformanceLog::new("run001");
1199 log.record(PerfEntry::new("md", "force", 20.0, 500.0, 8));
1200 log.record(PerfEntry::new("md", "integrate", 5.0, 100.0, 8));
1201 assert!((log.peak_memory_mb() - 500.0).abs() < 1e-10);
1202 }
1203
1204 #[test]
1205 fn test_performance_log_filter_by_crate() {
1206 let mut log = PerformanceLog::new("run001");
1207 log.record(PerfEntry::new("lbm", "stream", 5.0, 50.0, 4));
1208 log.record(PerfEntry::new("md", "force", 10.0, 100.0, 8));
1209 let lbm_entries = log.filter_by_crate("lbm");
1210 assert_eq!(lbm_entries.len(), 1);
1211 }
1212
1213 #[test]
1214 fn test_performance_log_to_csv() {
1215 let mut log = PerformanceLog::new("run001");
1216 log.record(PerfEntry::new("lbm", "stream", 5.0, 50.0, 4));
1217 let csv = log.to_csv();
1218 assert!(csv.contains("lbm"));
1219 assert!(csv.contains("stream"));
1220 }
1221
1222 #[test]
1223 fn test_scientific_json_new() {
1224 let sj = ScientificJson::new("simulation");
1225 assert_eq!(sj.name, "simulation");
1226 }
1227
1228 #[test]
1229 fn test_scientific_json_add_array() {
1230 let mut sj = ScientificJson::new("sim");
1231 sj.add_array("velocity", vec![1.0, 2.0, 3.0]);
1232 assert_eq!(sj.get_array("velocity"), Some([1.0, 2.0, 3.0].as_slice()));
1233 }
1234
1235 #[test]
1236 fn test_scientific_json_to_json_contains_base64() {
1237 let mut sj = ScientificJson::new("sim");
1238 sj.add_array("pressure", vec![1.0, 2.0]);
1239 let json = sj.to_json();
1240 assert!(json.contains("base64"));
1241 assert!(json.contains("pressure"));
1242 }
1243
1244 #[test]
1245 fn test_scientific_json_scalar() {
1246 let mut sj = ScientificJson::new("sim");
1247 sj.set_scalar("dt", 0.001);
1248 let json = sj.to_json();
1249 let val = ScientificJson::parse_scalar(&json, "dt");
1250 assert!(val.is_some());
1251 assert!((val.unwrap() - 0.001).abs() < 1e-10);
1252 }
1253}