1#![allow(clippy::manual_div_ceil)]
6use oxiphysics_core::math::Vec3;
7use std::io::Write;
8
9use super::types::{
10 XdmfAttribute, XdmfFieldDescriptor, XdmfMeshPatch, XdmfMeshTimeSeries, XdmfTopologyType,
11 XdmfUniformGrid,
12};
13
14pub fn write_xdmf_particles<W: Write>(
34 writer: &mut W,
35 positions: &[Vec3],
36 scalar_fields: &[(&str, &[f64])],
37) -> std::io::Result<()> {
38 let n = positions.len();
39 writeln!(writer, "<?xml version=\"1.0\"?>")?;
40 writeln!(writer, "<Xdmf Version=\"3.0\">")?;
41 writeln!(writer, " <Domain>")?;
42 writeln!(writer, " <Grid Name=\"particles\" GridType=\"Uniform\">")?;
43 writeln!(
44 writer,
45 " <Topology TopologyType=\"Polyvertex\" NumberOfElements=\"{}\"/>",
46 n
47 )?;
48 writeln!(writer, " <Geometry GeometryType=\"XYZ\">")?;
49 writeln!(
50 writer,
51 " <DataItem Format=\"XML\" Dimensions=\"{} 3\">",
52 n
53 )?;
54 for p in positions {
55 writeln!(writer, " {} {} {}", p.x, p.y, p.z)?;
56 }
57 writeln!(writer, " </DataItem>")?;
58 writeln!(writer, " </Geometry>")?;
59 for (name, values) in scalar_fields {
60 writeln!(
61 writer,
62 " <Attribute Name=\"{}\" AttributeType=\"Scalar\" Center=\"Node\">",
63 name
64 )?;
65 writeln!(
66 writer,
67 " <DataItem Format=\"XML\" Dimensions=\"{}\">",
68 n
69 )?;
70 write!(writer, " ")?;
71 for (i, v) in values.iter().enumerate() {
72 if i > 0 {
73 write!(writer, " ")?;
74 }
75 write!(writer, "{}", v)?;
76 }
77 writeln!(writer)?;
78 writeln!(writer, " </DataItem>")?;
79 writeln!(writer, " </Attribute>")?;
80 }
81 writeln!(writer, " </Grid>")?;
82 writeln!(writer, " </Domain>")?;
83 writeln!(writer, "</Xdmf>")?;
84 Ok(())
85}
86pub fn write_xdmf_temporal<W: Write>(
92 writer: &mut W,
93 timesteps: &[(f64, &[Vec3])],
94) -> std::io::Result<()> {
95 writeln!(writer, "<?xml version=\"1.0\"?>")?;
96 writeln!(writer, "<Xdmf Version=\"3.0\">")?;
97 writeln!(writer, " <Domain>")?;
98 writeln!(
99 writer,
100 " <Grid Name=\"TimeSeries\" GridType=\"Collection\" CollectionType=\"Temporal\">"
101 )?;
102 for (time, positions) in timesteps {
103 let n = positions.len();
104 writeln!(
105 writer,
106 " <Grid Name=\"particles\" GridType=\"Uniform\">"
107 )?;
108 writeln!(writer, " <Time Value=\"{}\"/>", time)?;
109 writeln!(
110 writer,
111 " <Topology TopologyType=\"Polyvertex\" NumberOfElements=\"{}\"/>",
112 n
113 )?;
114 writeln!(writer, " <Geometry GeometryType=\"XYZ\">")?;
115 writeln!(
116 writer,
117 " <DataItem Format=\"XML\" Dimensions=\"{} 3\">",
118 n
119 )?;
120 for p in *positions {
121 writeln!(writer, " {} {} {}", p.x, p.y, p.z)?;
122 }
123 writeln!(writer, " </DataItem>")?;
124 writeln!(writer, " </Geometry>")?;
125 writeln!(writer, " </Grid>")?;
126 }
127 writeln!(writer, " </Grid>")?;
128 writeln!(writer, " </Domain>")?;
129 writeln!(writer, "</Xdmf>")?;
130 Ok(())
131}
132#[allow(dead_code)]
134pub fn write_xdmf_with_attributes(
135 path: &str,
136 hdf5_path: &str,
137 n_nodes: usize,
138 n_elements: usize,
139 topology: &str,
140 attributes: &[XdmfAttribute],
141) -> std::io::Result<()> {
142 let mut f = std::fs::File::create(path)?;
143 writeln!(f, "<?xml version=\"1.0\"?>")?;
144 writeln!(f, "<Xdmf Version=\"3.0\">")?;
145 writeln!(f, " <Domain>")?;
146 writeln!(f, " <Grid Name=\"mesh\" GridType=\"Uniform\">")?;
147 writeln!(
148 f,
149 " <Topology TopologyType=\"{}\" NumberOfElements=\"{}\"/>",
150 topology, n_elements
151 )?;
152 writeln!(f, " <Geometry GeometryType=\"XYZ\">")?;
153 writeln!(
154 f,
155 " <DataItem Format=\"HDF\" Dimensions=\"{} 3\">{}:/coordinates</DataItem>",
156 n_nodes, hdf5_path
157 )?;
158 writeln!(f, " </Geometry>")?;
159 for attr in attributes {
160 let attr_type = if attr.n_components == 1 {
161 "Scalar"
162 } else {
163 "Vector"
164 };
165 let dims = if attr.n_components == 1 {
166 format!("{}", n_nodes)
167 } else {
168 format!("{} {}", n_nodes, attr.n_components)
169 };
170 writeln!(
171 f,
172 " <Attribute Name=\"{}\" AttributeType=\"{}\" Center=\"{}\">",
173 attr.name, attr_type, attr.center
174 )?;
175 writeln!(
176 f,
177 " <DataItem Format=\"HDF\" Dimensions=\"{}\">{}</DataItem>",
178 dims, attr.hdf5_path
179 )?;
180 writeln!(f, " </Attribute>")?;
181 }
182 writeln!(f, " </Grid>")?;
183 writeln!(f, " </Domain>")?;
184 writeln!(f, "</Xdmf>")?;
185 Ok(())
186}
187#[allow(dead_code)]
191pub fn parse_xdmf_topology(content: &str) -> Vec<(String, usize)> {
192 let mut result = Vec::new();
193 for line in content.lines() {
194 let trimmed = line.trim();
195 if !trimmed.contains("Topology") {
196 continue;
197 }
198 let topo_type = if let Some(start) = trimmed.find("TopologyType=\"") {
199 let rest = &trimmed[start + 14..];
200 rest.find('"').map(|end| rest[..end].to_string())
201 } else {
202 None
203 };
204 let n_elements = if let Some(start) = trimmed.find("NumberOfElements=\"") {
205 let rest = &trimmed[start + 18..];
206 rest.find('"')
207 .and_then(|end| rest[..end].parse::<usize>().ok())
208 } else {
209 None
210 };
211 if let (Some(t), Some(n)) = (topo_type, n_elements) {
212 result.push((t, n));
213 }
214 }
215 result
216}
217#[allow(dead_code)]
219pub fn write_xdmf_uniform_grid(path: &str, params: &XdmfUniformGrid) -> std::io::Result<()> {
220 let [nx, ny, nz] = params.dimensions;
221 let [ox, oy, oz] = params.origin;
222 let [dx, dy, dz] = params.spacing;
223 let mut f = std::fs::File::create(path)?;
224 writeln!(f, "<?xml version=\"1.0\"?>")?;
225 writeln!(f, "<Xdmf Version=\"3.0\">")?;
226 writeln!(f, " <Domain>")?;
227 writeln!(
228 f,
229 " <Grid Name=\"{}\" GridType=\"Uniform\">",
230 params.name
231 )?;
232 writeln!(
233 f,
234 " <Topology TopologyType=\"3DCoRectMesh\" Dimensions=\"{} {} {}\"/>",
235 nz, ny, nx
236 )?;
237 writeln!(f, " <Geometry GeometryType=\"ORIGIN_DXDYDZ\">")?;
238 writeln!(
239 f,
240 " <DataItem Format=\"XML\" Dimensions=\"3\">{} {} {}</DataItem>",
241 oz, oy, ox
242 )?;
243 writeln!(
244 f,
245 " <DataItem Format=\"XML\" Dimensions=\"3\">{} {} {}</DataItem>",
246 dz, dy, dx
247 )?;
248 writeln!(f, " </Geometry>")?;
249 writeln!(f, " </Grid>")?;
250 writeln!(f, " </Domain>")?;
251 writeln!(f, "</Xdmf>")?;
252 Ok(())
253}
254#[allow(dead_code)]
256pub fn write_xdmf_hdf5_reference(filename: &str, dataset_path: &str) -> String {
257 format!(
258 "<DataItem Format=\"HDF\" Dimensions=\"1\">\n {}:{}\n</DataItem>",
259 filename, dataset_path
260 )
261}
262#[allow(dead_code)]
274pub fn xdmf_vector_attribute(name: &str, vectors: &[[f64; 3]]) -> String {
275 let n = vectors.len();
276 let mut s = String::new();
277 s.push_str(&format!(
278 " <Attribute Name=\"{}\" AttributeType=\"Vector\" Center=\"Node\">\n",
279 name
280 ));
281 s.push_str(&format!(
282 " <DataItem Format=\"XML\" Dimensions=\"{} 3\">\n",
283 n
284 ));
285 for v in vectors {
286 s.push_str(&format!(" {} {} {}\n", v[0], v[1], v[2]));
287 }
288 s.push_str(" </DataItem>\n");
289 s.push_str(" </Attribute>\n");
290 s
291}
292#[allow(dead_code)]
297pub fn xdmf_tensor6_attribute(name: &str, tensors: &[[f64; 6]]) -> String {
298 let n = tensors.len();
299 let mut s = String::new();
300 s.push_str(&format!(
301 " <Attribute Name=\"{}\" AttributeType=\"Tensor6\" Center=\"Node\">\n",
302 name
303 ));
304 s.push_str(&format!(
305 " <DataItem Format=\"XML\" Dimensions=\"{} 6\">\n",
306 n
307 ));
308 for t in tensors {
309 s.push_str(&format!(
310 " {} {} {} {} {} {}\n",
311 t[0], t[1], t[2], t[3], t[4], t[5]
312 ));
313 }
314 s.push_str(" </DataItem>\n");
315 s.push_str(" </Attribute>\n");
316 s
317}
318#[allow(dead_code)]
324pub fn write_xdmf_unstructured<W: Write>(
325 writer: &mut W,
326 topo: XdmfTopologyType,
327 nodes: &[[f64; 3]],
328 connectivity: &[usize],
329 scalar_fields: &[(&str, &[f64])],
330) -> std::io::Result<()> {
331 let n_nodes = nodes.len();
332 let npe = topo.nodes_per_element();
333 let n_elements = connectivity.len().checked_div(npe).unwrap_or(0);
334 writeln!(writer, "<?xml version=\"1.0\"?>")?;
335 writeln!(writer, "<Xdmf Version=\"3.0\">")?;
336 writeln!(writer, " <Domain>")?;
337 writeln!(writer, " <Grid Name=\"mesh\" GridType=\"Uniform\">")?;
338 writeln!(
339 writer,
340 " <Topology TopologyType=\"{}\" NumberOfElements=\"{}\">",
341 topo.xdmf_name(),
342 n_elements
343 )?;
344 writeln!(
345 writer,
346 " <DataItem Format=\"XML\" Dimensions=\"{} {}\">",
347 n_elements, npe
348 )?;
349 for chunk in connectivity.chunks(npe) {
350 let row: Vec<String> = chunk.iter().map(|&i| i.to_string()).collect();
351 writeln!(writer, " {}", row.join(" "))?;
352 }
353 writeln!(writer, " </DataItem>")?;
354 writeln!(writer, " </Topology>")?;
355 writeln!(writer, " <Geometry GeometryType=\"XYZ\">")?;
356 writeln!(
357 writer,
358 " <DataItem Format=\"XML\" Dimensions=\"{} 3\">",
359 n_nodes
360 )?;
361 for p in nodes {
362 writeln!(writer, " {} {} {}", p[0], p[1], p[2])?;
363 }
364 writeln!(writer, " </DataItem>")?;
365 writeln!(writer, " </Geometry>")?;
366 for (name, values) in scalar_fields {
367 writeln!(
368 writer,
369 " <Attribute Name=\"{}\" AttributeType=\"Scalar\" Center=\"Node\">",
370 name
371 )?;
372 writeln!(
373 writer,
374 " <DataItem Format=\"XML\" Dimensions=\"{}\">",
375 values.len()
376 )?;
377 write!(writer, " ")?;
378 for (i, v) in values.iter().enumerate() {
379 if i > 0 {
380 write!(writer, " ")?;
381 }
382 write!(writer, "{}", v)?;
383 }
384 writeln!(writer)?;
385 writeln!(writer, " </DataItem>")?;
386 writeln!(writer, " </Attribute>")?;
387 }
388 writeln!(writer, " </Grid>")?;
389 writeln!(writer, " </Domain>")?;
390 writeln!(writer, "</Xdmf>")?;
391 Ok(())
392}
393#[allow(dead_code)]
397pub fn xdmf_scalar_data_item(values: &[f64]) -> String {
398 let mut s = format!(
399 "<DataItem Format=\"XML\" Dimensions=\"{}\">\n ",
400 values.len()
401 );
402 for (i, v) in values.iter().enumerate() {
403 if i > 0 {
404 s.push(' ');
405 }
406 s.push_str(&format!("{}", v));
407 }
408 s.push_str("\n</DataItem>");
409 s
410}
411#[allow(dead_code)]
413pub fn xdmf_vector_data_item(vectors: &[[f64; 3]]) -> String {
414 let n = vectors.len();
415 let mut s = format!("<DataItem Format=\"XML\" Dimensions=\"{} 3\">\n", n);
416 for v in vectors {
417 s.push_str(&format!(" {} {} {}\n", v[0], v[1], v[2]));
418 }
419 s.push_str("</DataItem>");
420 s
421}
422#[allow(dead_code)]
424pub fn xdmf_time_element(t: f64) -> String {
425 format!("<Time Value=\"{}\"/>", t)
426}
427#[allow(dead_code)]
429pub fn total_node_count(series: &XdmfMeshTimeSeries) -> usize {
430 series.steps.iter().map(|s| s.nodes.len()).sum()
431}
432#[allow(dead_code)]
434pub fn peak_element_step(series: &XdmfMeshTimeSeries) -> Option<usize> {
435 series
436 .steps
437 .iter()
438 .enumerate()
439 .max_by_key(|(_, s)| s.n_elements())
440 .map(|(i, _)| i)
441}
442#[cfg(test)]
443mod tests {
444 use super::*;
445 use crate::xdmf::types::*;
446 fn make_positions(n: usize) -> Vec<Vec3> {
447 (0..n)
448 .map(|i| Vec3::new(i as f64, i as f64 * 0.5, 0.0))
449 .collect()
450 }
451 #[test]
452 fn test_xdmf_valid_xml() {
453 let positions = make_positions(3);
454 let mut buf = Vec::new();
455 write_xdmf_particles(&mut buf, &positions, &[]).unwrap();
456 let s = String::from_utf8(buf).unwrap();
457 assert!(s.contains("<?xml"), "missing XML declaration");
458 assert!(s.contains("<Xdmf"), "missing Xdmf root element");
459 assert!(s.contains("<Grid"), "missing Grid element");
460 }
461 #[test]
462 fn test_xdmf_correct_particle_count() {
463 let positions = make_positions(5);
464 let mut buf = Vec::new();
465 write_xdmf_particles(&mut buf, &positions, &[]).unwrap();
466 let s = String::from_utf8(buf).unwrap();
467 assert!(
468 s.contains("NumberOfElements=\"5\""),
469 "expected NumberOfElements=\"5\" in output, got:\n{}",
470 s
471 );
472 }
473 #[test]
474 fn test_xdmf_scalar_field_included() {
475 let positions = make_positions(4);
476 let density = vec![1.0, 2.0, 3.0, 4.0];
477 let fields: &[(&str, &[f64])] = &[("density", &density)];
478 let mut buf = Vec::new();
479 write_xdmf_particles(&mut buf, &positions, fields).unwrap();
480 let s = String::from_utf8(buf).unwrap();
481 assert!(
482 s.contains("density"),
483 "scalar field name 'density' not found in output"
484 );
485 assert!(s.contains("Scalar"), "AttributeType Scalar not found");
486 }
487 #[test]
488 fn test_xdmf_temporal_multiple_steps() {
489 let pos0 = make_positions(3);
490 let pos1 = make_positions(3);
491 let steps: &[(f64, &[Vec3])] = &[(0.0, &pos0), (1.0, &pos1)];
492 let mut buf = Vec::new();
493 write_xdmf_temporal(&mut buf, steps).unwrap();
494 let s = String::from_utf8(buf).unwrap();
495 assert!(s.contains("Temporal"), "expected CollectionType Temporal");
496 assert!(
497 s.contains("Time Value=\"0\"") || s.contains("Time Value=\"0."),
498 "time step 0 not found"
499 );
500 assert!(
501 s.contains("Time Value=\"1\"") || s.contains("Time Value=\"1."),
502 "time step 1 not found"
503 );
504 }
505 #[test]
506 fn test_xdmf_empty_positions() {
507 let positions: Vec<Vec3> = vec![];
508 let mut buf = Vec::new();
509 write_xdmf_particles(&mut buf, &positions, &[]).unwrap();
510 let s = String::from_utf8(buf).unwrap();
511 assert!(s.contains("NumberOfElements=\"0\""));
512 }
513 #[test]
514 fn test_time_series_add_step() {
515 let mut ts = XdmfTimeSeries::new();
516 ts.add_step(0.0, vec![[0.0, 0.0, 0.0]], vec![]);
517 ts.add_step(1.0, vec![[1.0, 0.0, 0.0]], vec![]);
518 assert_eq!(ts.steps.len(), 2);
519 assert!((ts.steps[0].time - 0.0).abs() < 1e-10);
520 assert!((ts.steps[1].time - 1.0).abs() < 1e-10);
521 }
522 #[test]
523 fn test_time_series_to_xml() {
524 let mut ts = XdmfTimeSeries::new();
525 ts.add_step(
526 0.5,
527 vec![[1.0, 2.0, 3.0]],
528 vec![("density".to_string(), vec![1.5])],
529 );
530 let xml = ts.to_xml();
531 assert!(xml.contains("Temporal"));
532 assert!(xml.contains("Time Value=\"0.5\""));
533 assert!(xml.contains("density"));
534 assert!(xml.contains("1 2 3"));
535 }
536 #[test]
537 fn test_time_series_multiple_scalars() {
538 let mut ts = XdmfTimeSeries::new();
539 ts.add_step(
540 0.0,
541 vec![[0.0; 3]],
542 vec![
543 ("temperature".to_string(), vec![300.0]),
544 ("pressure".to_string(), vec![101325.0]),
545 ],
546 );
547 let xml = ts.to_xml();
548 assert!(xml.contains("temperature"));
549 assert!(xml.contains("pressure"));
550 }
551 #[test]
552 fn test_xdmf_reader_from_xml() {
553 let mut ts = XdmfTimeSeries::new();
554 ts.add_step(0.0, vec![[0.0; 3]; 3], vec![]);
555 ts.add_step(1.0, vec![[1.0; 3]; 5], vec![]);
556 let xml = ts.to_xml();
557 let steps = XdmfReader::from_xml(&xml).unwrap();
558 assert_eq!(steps.len(), 2);
559 assert!((steps[0].time - 0.0).abs() < 1e-10);
560 assert_eq!(steps[0].n_points, 3);
561 assert!((steps[1].time - 1.0).abs() < 1e-10);
562 assert_eq!(steps[1].n_points, 5);
563 }
564 #[test]
565 fn test_xdmf_reader_empty_xml() {
566 let steps = XdmfReader::from_xml("").unwrap();
567 assert!(steps.is_empty());
568 }
569 #[test]
570 fn test_xdmf_hdf5_reference() {
571 let ref_str = write_xdmf_hdf5_reference("data.h5", "/positions");
572 assert!(ref_str.contains("data.h5:/positions"));
573 assert!(ref_str.contains("HDF"));
574 }
575 #[test]
576 fn test_time_series_empty() {
577 let ts = XdmfTimeSeries::new();
578 let xml = ts.to_xml();
579 assert!(xml.contains("Temporal"));
580 assert!(xml.contains("<Xdmf"));
581 assert!(xml.contains("</Xdmf>"));
582 }
583 #[test]
584 fn test_xdmf_step_n_points() {
585 let mut ts = XdmfTimeSeries::new();
586 let positions: Vec<[f64; 3]> = (0..10).map(|i| [i as f64, 0.0, 0.0]).collect();
587 ts.add_step(0.0, positions, vec![]);
588 assert_eq!(ts.steps[0].n_points, 10);
589 }
590 #[test]
591 fn test_vector_attribute_basic() {
592 let vecs = vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
593 let s = xdmf_vector_attribute("velocity", &vecs);
594 assert!(s.contains("velocity"));
595 assert!(s.contains("Vector"));
596 assert!(s.contains("2 3"));
597 assert!(s.contains("1 0 0"));
598 }
599 #[test]
600 fn test_vector_attribute_empty() {
601 let s = xdmf_vector_attribute("v", &[]);
602 assert!(s.contains("0 3"));
603 }
604 #[test]
605 fn test_tensor6_attribute_basic() {
606 let tensors = vec![[1.0, 2.0, 3.0, 0.5, 0.1, 0.2]];
607 let s = xdmf_tensor6_attribute("stress", &tensors);
608 assert!(s.contains("stress"));
609 assert!(s.contains("Tensor6"));
610 assert!(s.contains("1 6"));
611 assert!(s.contains("0.5"));
612 }
613 #[test]
614 fn test_unstructured_triangle_mesh() {
615 let nodes = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
616 let connectivity = vec![0usize, 1, 2];
617 let mut buf = Vec::new();
618 write_xdmf_unstructured(
619 &mut buf,
620 XdmfTopologyType::Triangle,
621 &nodes,
622 &connectivity,
623 &[],
624 )
625 .unwrap();
626 let s = String::from_utf8(buf).unwrap();
627 assert!(s.contains("Triangle"), "topology type not found");
628 assert!(s.contains("NumberOfElements=\"1\""));
629 assert!(s.contains("0 1 2"));
630 }
631 #[test]
632 fn test_unstructured_tet_mesh() {
633 let nodes: Vec<[f64; 3]> = (0..4).map(|i| [i as f64, 0.0, 0.0]).collect();
634 let connectivity = vec![0usize, 1, 2, 3];
635 let mut buf = Vec::new();
636 write_xdmf_unstructured(
637 &mut buf,
638 XdmfTopologyType::Tetrahedron,
639 &nodes,
640 &connectivity,
641 &[],
642 )
643 .unwrap();
644 let s = String::from_utf8(buf).unwrap();
645 assert!(s.contains("Tetrahedron"));
646 assert!(s.contains("NumberOfElements=\"1\""));
647 }
648 #[test]
649 fn test_unstructured_with_scalar_field() {
650 let nodes = vec![[0.0; 3]; 3];
651 let connectivity = vec![0usize, 1, 2];
652 let pressure = vec![1.0, 2.0, 3.0];
653 let mut buf = Vec::new();
654 write_xdmf_unstructured(
655 &mut buf,
656 XdmfTopologyType::Triangle,
657 &nodes,
658 &connectivity,
659 &[("pressure", &pressure)],
660 )
661 .unwrap();
662 let s = String::from_utf8(buf).unwrap();
663 assert!(s.contains("pressure"));
664 }
665 #[test]
666 fn topology_type_nodes_per_element() {
667 assert_eq!(XdmfTopologyType::Triangle.nodes_per_element(), 3);
668 assert_eq!(XdmfTopologyType::Tetrahedron.nodes_per_element(), 4);
669 assert_eq!(XdmfTopologyType::Hexahedron.nodes_per_element(), 8);
670 assert_eq!(XdmfTopologyType::Quadrilateral.nodes_per_element(), 4);
671 }
672 #[test]
673 fn schema_validate_ok() {
674 let schema = XdmfSchema::new("Polyvertex", vec!["density".to_string()]);
675 let positions = make_positions(2);
676 let density = vec![1.0, 2.0];
677 let fields: &[(&str, &[f64])] = &[("density", &density)];
678 let mut buf = Vec::new();
679 write_xdmf_particles(&mut buf, &positions, fields).unwrap();
680 let xml = String::from_utf8(buf).unwrap();
681 let errors = schema.validate(&xml);
682 assert!(errors.is_empty(), "unexpected errors: {errors:?}");
683 }
684 #[test]
685 fn schema_validate_missing_topology() {
686 let schema = XdmfSchema::new("Triangle", vec![]);
687 let positions = make_positions(3);
688 let mut buf = Vec::new();
689 write_xdmf_particles(&mut buf, &positions, &[]).unwrap();
690 let xml = String::from_utf8(buf).unwrap();
691 let errors = schema.validate(&xml);
692 assert!(
693 !errors.is_empty(),
694 "should report missing topology Triangle"
695 );
696 }
697 #[test]
698 fn schema_validate_missing_attribute() {
699 let schema = XdmfSchema::new("Polyvertex", vec!["velocity".to_string()]);
700 let positions = make_positions(2);
701 let mut buf = Vec::new();
702 write_xdmf_particles(&mut buf, &positions, &[]).unwrap();
703 let xml = String::from_utf8(buf).unwrap();
704 let errors = schema.validate(&xml);
705 assert!(!errors.is_empty());
706 }
707 #[test]
708 fn time_series_vector_step_encodes_xyz() {
709 let mut ts = XdmfTimeSeries::new();
710 ts.add_step_with_vectors(
711 0.0,
712 vec![[0.0; 3]],
713 vec![],
714 vec![("velocity".to_string(), vec![[1.0, 2.0, 3.0]])],
715 );
716 let xml = ts.to_xml();
717 assert!(xml.contains("velocity_x"), "should have velocity_x field");
718 assert!(xml.contains("velocity_y"));
719 assert!(xml.contains("velocity_z"));
720 }
721 #[test]
722 fn time_series_times() {
723 let mut ts = XdmfTimeSeries::new();
724 ts.add_step(0.0, vec![], vec![]);
725 ts.add_step(0.5, vec![], vec![]);
726 ts.add_step(1.0, vec![], vec![]);
727 assert_eq!(ts.times(), vec![0.0, 0.5, 1.0]);
728 }
729 #[test]
730 fn time_series_total_particle_count() {
731 let mut ts = XdmfTimeSeries::new();
732 ts.add_step(0.0, vec![[0.0; 3]; 5], vec![]);
733 ts.add_step(1.0, vec![[0.0; 3]; 3], vec![]);
734 assert_eq!(ts.total_particle_count(), 8);
735 }
736 #[test]
737 fn time_series_default() {
738 let ts = XdmfTimeSeries::default();
739 assert!(ts.steps.is_empty());
740 }
741 fn make_tri_step(time: f64) -> XdmfMeshStep {
742 XdmfMeshStep {
743 time,
744 nodes: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
745 connectivity: vec![0, 1, 2],
746 topology: XdmfTopologyType::Triangle,
747 node_scalars: vec![],
748 node_vectors: vec![],
749 }
750 }
751 #[test]
752 fn mesh_series_add_and_len() {
753 let mut ms = XdmfMeshTimeSeries::new();
754 ms.add_step(make_tri_step(0.0));
755 ms.add_step(make_tri_step(1.0));
756 assert_eq!(ms.len(), 2);
757 assert!(!ms.is_empty());
758 }
759 #[test]
760 fn mesh_series_to_xml_contains_temporal() {
761 let mut ms = XdmfMeshTimeSeries::new();
762 ms.add_step(make_tri_step(0.0));
763 let xml = ms.to_xml();
764 assert!(xml.contains("Temporal"));
765 assert!(xml.contains("Triangle"));
766 assert!(xml.contains("0 1 2"));
767 }
768 #[test]
769 fn mesh_series_scalar_field_in_xml() {
770 let mut step = make_tri_step(0.0);
771 step.node_scalars
772 .push(("temperature".into(), vec![300.0, 310.0, 305.0]));
773 let mut ms = XdmfMeshTimeSeries::new();
774 ms.add_step(step);
775 let xml = ms.to_xml();
776 assert!(xml.contains("temperature"));
777 assert!(xml.contains("310"));
778 }
779 #[test]
780 fn mesh_series_vector_field_in_xml() {
781 let mut step = make_tri_step(0.0);
782 step.node_vectors.push((
783 "velocity".into(),
784 vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
785 ));
786 let mut ms = XdmfMeshTimeSeries::new();
787 ms.add_step(step);
788 let xml = ms.to_xml();
789 assert!(xml.contains("velocity"));
790 assert!(xml.contains("Vector"));
791 }
792 #[test]
793 fn mesh_series_times() {
794 let mut ms = XdmfMeshTimeSeries::new();
795 ms.add_step(make_tri_step(0.0));
796 ms.add_step(make_tri_step(0.5));
797 ms.add_step(make_tri_step(1.0));
798 assert_eq!(ms.times(), vec![0.0, 0.5, 1.0]);
799 }
800 #[test]
801 fn mesh_step_n_elements() {
802 let step = make_tri_step(0.0);
803 assert_eq!(step.n_elements(), 1);
804 }
805 #[test]
806 fn hdf5_builder_basic() {
807 let b = Hdf5DataItemBuilder::new("data.h5");
808 let xml = b.build("positions", "100 3");
809 assert!(xml.contains("data.h5:/positions"));
810 assert!(xml.contains("100 3"));
811 assert!(xml.contains("HDF"));
812 }
813 #[test]
814 fn hdf5_builder_with_group() {
815 let b = Hdf5DataItemBuilder::new("sim.h5").group("/step0");
816 let xml = b.build("velocity", "50 3");
817 assert!(xml.contains("sim.h5:/step0/velocity"));
818 }
819 #[test]
820 fn scalar_data_item_format() {
821 let item = xdmf_scalar_data_item(&[1.0, 2.0, 3.0]);
822 assert!(item.contains("Dimensions=\"3\""));
823 assert!(item.contains("1 2 3"));
824 }
825 #[test]
826 fn vector_data_item_format() {
827 let item = xdmf_vector_data_item(&[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]);
828 assert!(item.contains("2 3"));
829 assert!(item.contains("1 0 0"));
830 }
831 #[test]
832 fn time_element_format() {
833 let t = xdmf_time_element(3.125);
834 assert!(t.contains("3.125"));
835 assert!(t.contains("<Time"));
836 }
837 #[test]
838 fn total_node_count_basic() {
839 let mut ms = XdmfMeshTimeSeries::new();
840 ms.add_step(make_tri_step(0.0));
841 ms.add_step(make_tri_step(1.0));
842 assert_eq!(total_node_count(&ms), 6);
843 }
844 #[test]
845 fn peak_element_step_basic() {
846 let mut ms = XdmfMeshTimeSeries::new();
847 ms.add_step(make_tri_step(0.0));
848 ms.add_step(XdmfMeshStep {
849 time: 1.0,
850 nodes: vec![[0.0; 3]; 4],
851 connectivity: vec![0, 1, 2, 1, 2, 3],
852 topology: XdmfTopologyType::Triangle,
853 node_scalars: vec![],
854 node_vectors: vec![],
855 });
856 assert_eq!(peak_element_step(&ms), Some(1));
857 }
858 #[test]
859 fn peak_element_step_empty_series() {
860 let ms = XdmfMeshTimeSeries::new();
861 assert_eq!(peak_element_step(&ms), None);
862 }
863 #[test]
864 fn test_write_collection_creates_file_with_timestep_count() {
865 let mut ts = XdmfTimeSeriesHdf5::new();
866 ts.timesteps = vec![0.0, 1.0, 2.0];
867 ts.hdf5_paths = vec!["a.h5".into(), "b.h5".into(), "c.h5".into()];
868 ts.attribute_names = vec!["temperature".into()];
869 let path = "/tmp/test_write_collection.xmf";
870 ts.write_collection(path, 10, 5, "Triangle").unwrap();
871 let content = std::fs::read_to_string(path).unwrap();
872 let count = content.matches("<Time Value=").count();
873 assert_eq!(count, 3, "expected 3 timestep entries, got {}", count);
874 assert!(content.contains("CollectionType=\"Temporal\""));
875 assert!(content.contains("temperature"));
876 }
877 #[test]
878 fn test_parse_xdmf_topology_finds_topology() {
879 let xml = r#"<?xml version="1.0"?>
880<Xdmf Version="3.0">
881 <Domain>
882 <Grid Name="mesh" GridType="Uniform">
883 <Topology TopologyType="Triangle" NumberOfElements="42"/>
884 </Grid>
885 </Domain>
886</Xdmf>"#;
887 let topologies = parse_xdmf_topology(xml);
888 assert_eq!(topologies.len(), 1);
889 assert_eq!(topologies[0].0, "Triangle");
890 assert_eq!(topologies[0].1, 42);
891 }
892 #[test]
893 fn test_write_xdmf_uniform_grid_contains_grid_tag() {
894 let params = XdmfUniformGrid {
895 name: "volume".to_string(),
896 dimensions: [4, 5, 6],
897 origin: [0.0, 0.0, 0.0],
898 spacing: [1.0, 1.0, 1.0],
899 };
900 let path = "/tmp/test_uniform_grid.xmf";
901 write_xdmf_uniform_grid(path, ¶ms).unwrap();
902 let content = std::fs::read_to_string(path).unwrap();
903 assert!(content.contains("<Grid"), "missing Grid tag");
904 assert!(content.contains("volume"), "missing grid name");
905 assert!(content.contains("3DCoRectMesh"), "missing topology type");
906 assert!(content.contains("ORIGIN_DXDYDZ"), "missing geometry type");
907 }
908 #[test]
909 fn test_write_xdmf_with_attributes_creates_valid_file() {
910 let attrs = vec![XdmfAttribute {
911 name: "pressure".to_string(),
912 center: "Node".to_string(),
913 n_components: 1,
914 hdf5_path: "data.h5:/pressure".to_string(),
915 }];
916 let path = "/tmp/test_xdmf_with_attrs.xmf";
917 write_xdmf_with_attributes(path, "data.h5", 20, 10, "Tetrahedron", &attrs).unwrap();
918 let content = std::fs::read_to_string(path).unwrap();
919 assert!(content.contains("pressure"));
920 assert!(content.contains("Tetrahedron"));
921 assert!(content.contains("data.h5:/coordinates"));
922 }
923}
924#[cfg(test)]
925mod tests_xdmf_ext {
926
927 use crate::xdmf::types::*;
928 #[test]
929 fn add_frame_increments_step_count() {
930 let mut ts = XdmfTimeSeries::new();
931 ts.add_frame(0.0, vec![[0.0; 3]; 5]);
932 ts.add_frame(1.0, vec![[1.0; 3]; 5]);
933 assert_eq!(ts.steps.len(), 2);
934 }
935 #[test]
936 fn add_frame_sets_n_points() {
937 let mut ts = XdmfTimeSeries::new();
938 ts.add_frame(0.0, vec![[0.0; 3]; 7]);
939 assert_eq!(ts.steps[0].n_points, 7);
940 }
941 #[test]
942 fn add_frame_with_scalars_persists_data() {
943 let mut ts = XdmfTimeSeries::new();
944 ts.add_frame_with_scalars(
945 0.5,
946 vec![[0.0; 3]; 3],
947 vec![("pressure".to_string(), vec![1.0, 2.0, 3.0])],
948 );
949 assert_eq!(ts.steps[0].scalar_fields.len(), 1);
950 assert_eq!(ts.steps[0].scalar_fields[0].0, "pressure");
951 }
952 #[test]
953 fn write_xml_produces_valid_xml_string() {
954 let mut ts = XdmfTimeSeries::new();
955 ts.add_frame(0.0, vec![[1.0, 2.0, 3.0]]);
956 let mut buf: Vec<u8> = Vec::new();
957 ts.write_xml(&mut buf).unwrap();
958 let s = String::from_utf8(buf).unwrap();
959 assert!(s.contains("<?xml"));
960 assert!(s.contains("Temporal"));
961 assert!(s.contains("Time Value=\"0\"") || s.contains("Time Value=\"0."));
962 }
963 #[test]
964 fn write_xml_matches_to_xml() {
965 let mut ts = XdmfTimeSeries::new();
966 ts.add_frame(1.5, vec![[0.0; 3]; 4]);
967 let expected = ts.to_xml();
968 let mut buf: Vec<u8> = Vec::new();
969 ts.write_xml(&mut buf).unwrap();
970 let actual = String::from_utf8(buf).unwrap();
971 assert_eq!(actual, expected);
972 }
973 #[test]
974 fn write_xml_to_file_creates_file() {
975 let mut ts = XdmfTimeSeries::new();
976 ts.add_frame(0.0, vec![[0.0; 3]; 2]);
977 let path = "/tmp/test_write_xml_ext.xmf";
978 ts.write_xml_to_file(path).unwrap();
979 let content = std::fs::read_to_string(path).unwrap();
980 assert!(content.contains("<Xdmf"));
981 }
982 #[test]
983 fn mixed_topology_xdmf_name() {
984 assert_eq!(XdmfTopologyType::Mixed.xdmf_name(), "Mixed");
985 }
986 #[test]
987 fn mixed_topology_nodes_per_element_zero() {
988 assert_eq!(XdmfTopologyType::Mixed.nodes_per_element(), 0);
989 }
990 #[test]
991 fn topology_type_names_cover_all_variants() {
992 let types = [
993 XdmfTopologyType::Triangle,
994 XdmfTopologyType::Tetrahedron,
995 XdmfTopologyType::Hexahedron,
996 XdmfTopologyType::Quadrilateral,
997 XdmfTopologyType::Mixed,
998 ];
999 let names: Vec<&str> = types.iter().map(|t| t.xdmf_name()).collect();
1000 assert!(names.contains(&"Triangle"));
1001 assert!(names.contains(&"Tetrahedron"));
1002 assert!(names.contains(&"Hexahedron"));
1003 assert!(names.contains(&"Quadrilateral"));
1004 assert!(names.contains(&"Mixed"));
1005 }
1006 #[test]
1007 fn add_frame_empty_positions() {
1008 let mut ts = XdmfTimeSeries::new();
1009 ts.add_frame(0.0, vec![]);
1010 assert_eq!(ts.steps[0].n_points, 0);
1011 let xml = ts.to_xml();
1012 assert!(xml.contains("<Xdmf"));
1013 }
1014 #[test]
1015 fn write_xml_multiple_frames_all_present() {
1016 let mut ts = XdmfTimeSeries::new();
1017 for i in 0..4 {
1018 ts.add_frame(i as f64, vec![[0.0; 3]; 2]);
1019 }
1020 let mut buf: Vec<u8> = Vec::new();
1021 ts.write_xml(&mut buf).unwrap();
1022 let s = String::from_utf8(buf).unwrap();
1023 let count = s.matches("<Time Value=").count();
1024 assert_eq!(count, 4);
1025 }
1026}
1027#[allow(dead_code)]
1031pub fn xdmf_tensor9_attribute(name: &str, tensors: &[[f64; 9]]) -> String {
1032 let n = tensors.len();
1033 let mut s = String::new();
1034 s.push_str(&format!(
1035 " <Attribute Name=\"{}\" AttributeType=\"Tensor\" Center=\"Node\">\n",
1036 name
1037 ));
1038 s.push_str(&format!(
1039 " <DataItem Format=\"XML\" Dimensions=\"{} 9\">\n",
1040 n
1041 ));
1042 for t in tensors {
1043 s.push_str(" ");
1044 for (i, c) in t.iter().enumerate() {
1045 if i > 0 {
1046 s.push(' ');
1047 }
1048 s.push_str(&format!("{}", c));
1049 }
1050 s.push('\n');
1051 }
1052 s.push_str(" </DataItem>\n");
1053 s.push_str(" </Attribute>\n");
1054 s
1055}
1056#[allow(dead_code)]
1058pub fn xdmf_is_well_formed(xml: &str) -> bool {
1059 xml.contains("<Xdmf")
1060 && xml.contains("Version=")
1061 && xml.contains("<Domain>")
1062 && xml.contains("</Domain>")
1063 && xml.contains("</Xdmf>")
1064}
1065#[allow(dead_code)]
1067pub fn xdmf_count_grids(xml: &str) -> usize {
1068 xml.matches("<Grid").count()
1069}
1070#[allow(dead_code)]
1072pub fn xdmf_count_attributes(xml: &str) -> usize {
1073 xml.matches("<Attribute").count()
1074}
1075#[cfg(test)]
1076mod tests_xdmf_new {
1077 use super::*;
1078 use crate::xdmf::Hdf5DataItemBuilder;
1079
1080 use crate::xdmf::XdmfMeshStep;
1081 use crate::xdmf::XdmfMeshTimeSeries;
1082 use crate::xdmf::XdmfMultiBlock;
1083
1084 use crate::xdmf::XdmfStructuredGrid;
1085
1086 use crate::xdmf::XdmfTimeSeriesHdf5;
1087 use crate::xdmf::XdmfWriter;
1088
1089 use crate::xdmf::total_node_count;
1090 use crate::xdmf::types::*;
1091
1092 use crate::xdmf::xdmf_is_well_formed;
1093 use crate::xdmf::xdmf_scalar_data_item;
1094 use crate::xdmf::xdmf_time_element;
1095 use crate::xdmf::xdmf_vector_data_item;
1096 #[test]
1097 fn structured_grid_node_count() {
1098 let g = XdmfStructuredGrid::new("test", 4, 5, 6, [0.0; 3], 1.0, 1.0, 1.0);
1099 assert_eq!(g.n_nodes(), 4 * 5 * 6);
1100 }
1101 #[test]
1102 fn structured_grid_cell_count() {
1103 let g = XdmfStructuredGrid::new("test", 4, 5, 6, [0.0; 3], 1.0, 1.0, 1.0);
1104 assert_eq!(g.n_cells(), 3 * 4 * 5);
1105 }
1106 #[test]
1107 fn structured_grid_to_xml_contains_topology() {
1108 let g = XdmfStructuredGrid::new("flow", 3, 3, 3, [0.0; 3], 0.5, 0.5, 0.5);
1109 let xml = g.to_xml();
1110 assert!(xml.contains("3DCoRectMesh"));
1111 assert!(xml.contains("ORIGIN_DXDYDZ"));
1112 assert!(xml.contains("flow"));
1113 }
1114 #[test]
1115 fn structured_grid_scalar_in_xml() {
1116 let mut g = XdmfStructuredGrid::new("g", 2, 2, 2, [0.0; 3], 1.0, 1.0, 1.0);
1117 let n = g.n_nodes();
1118 g.add_node_scalar("pressure", vec![1.0; n]);
1119 let xml = g.to_xml();
1120 assert!(xml.contains("pressure"));
1121 assert!(xml.contains("Center=\"Node\""));
1122 }
1123 #[test]
1124 fn structured_grid_cell_scalar_in_xml() {
1125 let mut g = XdmfStructuredGrid::new("g", 3, 3, 3, [0.0; 3], 1.0, 1.0, 1.0);
1126 let nc = g.n_cells();
1127 g.add_cell_scalar("vorticity", vec![0.5; nc]);
1128 let xml = g.to_xml();
1129 assert!(xml.contains("vorticity"));
1130 assert!(xml.contains("Center=\"Cell\""));
1131 }
1132 #[test]
1133 fn structured_grid_vector_in_xml() {
1134 let mut g = XdmfStructuredGrid::new("g", 2, 2, 2, [0.0; 3], 1.0, 1.0, 1.0);
1135 let n = g.n_nodes();
1136 g.add_node_vector("velocity", vec![[1.0, 0.0, 0.0]; n]);
1137 let xml = g.to_xml();
1138 assert!(xml.contains("velocity"));
1139 assert!(xml.contains("AttributeType=\"Vector\""));
1140 }
1141 #[test]
1142 fn structured_grid_origin_appears_in_geometry() {
1143 let g = XdmfStructuredGrid::new("g", 2, 2, 2, [1.0, 2.0, 3.0], 0.1, 0.2, 0.3);
1144 let xml = g.to_xml();
1145 assert!(xml.contains("3") && xml.contains("ORIGIN_DXDYDZ"));
1146 }
1147 #[test]
1148 fn xdmf_writer_basic_document() {
1149 let mut w = XdmfWriter::new();
1150 w.open_domain();
1151 w.open_grid("particles", "Uniform");
1152 w.write_polyvertex_topology(5);
1153 w.write_xyz_geometry(&[[0.0, 0.0, 0.0]; 5]);
1154 w.close_grid();
1155 w.close_domain();
1156 let xml = w.finish();
1157 assert!(xml.contains("<?xml"));
1158 assert!(xml.contains("<Xdmf"));
1159 assert!(xml.contains("Polyvertex"));
1160 assert!(xml.contains("particles"));
1161 assert!(xml.contains("</Xdmf>"));
1162 }
1163 #[test]
1164 fn xdmf_writer_temporal_collection() {
1165 let mut w = XdmfWriter::new();
1166 w.open_domain();
1167 w.open_temporal_collection("ts");
1168 for i in 0..3_usize {
1169 w.open_grid("step", "Uniform");
1170 w.write_time(i as f64 * 0.1);
1171 w.write_polyvertex_topology(2);
1172 w.write_xyz_geometry(&[[0.0; 3]; 2]);
1173 w.close_grid();
1174 }
1175 w.close_grid();
1176 w.close_domain();
1177 let xml = w.finish();
1178 let count = xml.matches("<Time Value=").count();
1179 assert_eq!(count, 3);
1180 assert!(xml.contains("CollectionType=\"Temporal\""));
1181 }
1182 #[test]
1183 fn xdmf_writer_scalar_attribute() {
1184 let mut w = XdmfWriter::new();
1185 w.open_domain();
1186 w.open_grid("g", "Uniform");
1187 w.write_polyvertex_topology(3);
1188 w.write_xyz_geometry(&[[0.0; 3]; 3]);
1189 w.write_scalar_attribute("density", "Node", &[1.0, 2.0, 3.0]);
1190 w.close_grid();
1191 w.close_domain();
1192 let xml = w.finish();
1193 assert!(xml.contains("density"));
1194 assert!(xml.contains("Scalar"));
1195 }
1196 #[test]
1197 fn xdmf_writer_vector_attribute() {
1198 let mut w = XdmfWriter::new();
1199 w.open_domain();
1200 w.open_grid("g", "Uniform");
1201 w.write_polyvertex_topology(2);
1202 w.write_xyz_geometry(&[[0.0; 3]; 2]);
1203 w.write_vector_attribute("velocity", "Node", &[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]);
1204 w.close_grid();
1205 w.close_domain();
1206 let xml = w.finish();
1207 assert!(xml.contains("velocity"));
1208 assert!(xml.contains("Vector"));
1209 }
1210 #[test]
1211 fn xdmf_writer_hdf5_attribute() {
1212 let mut w = XdmfWriter::new();
1213 w.open_domain();
1214 w.open_grid("g", "Uniform");
1215 w.write_polyvertex_topology(10);
1216 w.write_hdf5_attribute("temp", "Node", "Scalar", "10", "data.h5:/temperature");
1217 w.close_grid();
1218 w.close_domain();
1219 let xml = w.finish();
1220 assert!(xml.contains("HDF"));
1221 assert!(xml.contains("temperature"));
1222 }
1223 #[test]
1224 fn xdmf_writer_peek() {
1225 let mut w = XdmfWriter::new();
1226 w.open_domain();
1227 let preview = w.peek().to_string();
1228 assert!(preview.contains("<Domain>"));
1229 }
1230 #[test]
1231 fn tensor9_attribute_has_correct_dimensions() {
1232 let tensors = vec![[1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0f64]; 3];
1233 let xml = xdmf_tensor9_attribute("stress", &tensors);
1234 assert!(xml.contains("Tensor"));
1235 assert!(xml.contains("3 9"));
1236 assert!(xml.contains("stress"));
1237 }
1238 #[test]
1239 fn tensor9_attribute_empty() {
1240 let xml = xdmf_tensor9_attribute("empty_tensor", &[]);
1241 assert!(xml.contains("0 9"));
1242 }
1243 #[test]
1244 fn well_formed_valid_document() {
1245 let xml =
1246 "<?xml version=\"1.0\"?>\n<Xdmf Version=\"3.0\">\n <Domain>\n </Domain>\n</Xdmf>\n";
1247 assert!(xdmf_is_well_formed(xml));
1248 }
1249 #[test]
1250 fn well_formed_rejects_truncated() {
1251 let xml = "<Xdmf Version=\"3.0\">\n <Domain>\n";
1252 assert!(!xdmf_is_well_formed(xml));
1253 }
1254 #[test]
1255 fn count_grids_correct() {
1256 let mut w = XdmfWriter::new();
1257 w.open_domain();
1258 w.open_temporal_collection("ts");
1259 for _ in 0..3_usize {
1260 w.open_grid("g", "Uniform");
1261 w.close_grid();
1262 }
1263 w.close_grid();
1264 w.close_domain();
1265 let xml = w.finish();
1266 assert_eq!(xdmf_count_grids(&xml), 4);
1267 }
1268 #[test]
1269 fn count_attributes_correct() {
1270 let mut w = XdmfWriter::new();
1271 w.open_domain();
1272 w.open_grid("g", "Uniform");
1273 w.write_polyvertex_topology(2);
1274 w.write_xyz_geometry(&[[0.0; 3]; 2]);
1275 w.write_scalar_attribute("a", "Node", &[1.0, 2.0]);
1276 w.write_scalar_attribute("b", "Node", &[3.0, 4.0]);
1277 w.write_vector_attribute("v", "Node", &[[0.0; 3]; 2]);
1278 w.close_grid();
1279 w.close_domain();
1280 let xml = w.finish();
1281 assert_eq!(xdmf_count_attributes(&xml), 3);
1282 }
1283 #[test]
1284 fn multiblock_empty() {
1285 let mb = XdmfMultiBlock::new();
1286 assert!(mb.is_empty());
1287 assert_eq!(mb.len(), 0);
1288 }
1289 #[test]
1290 fn multiblock_spatial_collection() {
1291 let mut mb = XdmfMultiBlock::new();
1292 mb.add_block("block0", "<Grid Name=\"b0\" GridType=\"Uniform\"></Grid>");
1293 mb.add_block("block1", "<Grid Name=\"b1\" GridType=\"Uniform\"></Grid>");
1294 assert_eq!(mb.len(), 2);
1295 let xml = mb.to_xml();
1296 assert!(xml.contains("Spatial"));
1297 assert!(xml.contains("b0"));
1298 assert!(xml.contains("b1"));
1299 }
1300 #[test]
1301 fn multiblock_to_xml_well_formed() {
1302 let mut mb = XdmfMultiBlock::new();
1303 mb.add_block("g", "<Grid Name=\"g\" GridType=\"Uniform\"></Grid>");
1304 let xml = mb.to_xml();
1305 assert!(xdmf_is_well_formed(&xml));
1306 }
1307 #[test]
1308 fn hdf5_time_series_attribute_names() {
1309 let mut ts = XdmfTimeSeriesHdf5::new();
1310 ts.timesteps = vec![0.0, 1.0];
1311 ts.hdf5_paths = vec!["step0.h5".into(), "step1.h5".into()];
1312 ts.attribute_names = vec!["density".into(), "pressure".into()];
1313 let path = "/tmp/test_hdf5_series_attrs.xmf";
1314 ts.write_collection(path, 100, 50, "Tetrahedron").unwrap();
1315 let content = std::fs::read_to_string(path).unwrap();
1316 assert!(content.contains("density"));
1317 assert!(content.contains("pressure"));
1318 let time_count = content.matches("<Time Value=").count();
1319 assert_eq!(time_count, 2);
1320 }
1321 #[test]
1322 fn hdf5_data_item_builder_group() {
1323 let b = Hdf5DataItemBuilder::new("sim.h5").group("/results");
1324 let item = b.build("velocity", "100 3");
1325 assert!(item.contains("sim.h5:/results/velocity"));
1326 assert!(item.contains("100 3"));
1327 }
1328 #[test]
1329 fn hdf5_data_item_builder_root_group() {
1330 let b = Hdf5DataItemBuilder::new("data.h5");
1331 let item = b.build("coordinates", "50 3");
1332 assert!(item.contains("data.h5:/coordinates"));
1333 }
1334 #[test]
1335 fn write_xdmf_unstructured_tetra() {
1336 let nodes: Vec<[f64; 3]> = vec![
1337 [0.0, 0.0, 0.0],
1338 [1.0, 0.0, 0.0],
1339 [0.0, 1.0, 0.0],
1340 [0.0, 0.0, 1.0],
1341 ];
1342 let connectivity = vec![0, 1, 2, 3];
1343 let mut buf = Vec::new();
1344 write_xdmf_unstructured(
1345 &mut buf,
1346 XdmfTopologyType::Tetrahedron,
1347 &nodes,
1348 &connectivity,
1349 &[],
1350 )
1351 .unwrap();
1352 let s = String::from_utf8(buf).unwrap();
1353 assert!(s.contains("Tetrahedron"));
1354 assert!(s.contains("NumberOfElements=\"1\""));
1355 }
1356 #[test]
1357 fn write_xdmf_unstructured_hex() {
1358 let nodes = vec![[0.0f64; 3]; 8];
1359 let connectivity: Vec<usize> = (0..8).collect();
1360 let mut buf = Vec::new();
1361 write_xdmf_unstructured(
1362 &mut buf,
1363 XdmfTopologyType::Hexahedron,
1364 &nodes,
1365 &connectivity,
1366 &[],
1367 )
1368 .unwrap();
1369 let s = String::from_utf8(buf).unwrap();
1370 assert!(s.contains("Hexahedron"));
1371 assert!(s.contains("NumberOfElements=\"1\""));
1372 }
1373 #[test]
1374 fn xdmf_scalar_data_item_values_present() {
1375 let item = xdmf_scalar_data_item(&[1.0, 2.0, 3.0]);
1376 assert!(item.contains("Dimensions=\"3\""));
1377 assert!(item.contains("1"));
1378 assert!(item.contains("2"));
1379 assert!(item.contains("3"));
1380 }
1381 #[test]
1382 fn xdmf_vector_data_item_dimensions() {
1383 let item = xdmf_vector_data_item(&[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
1384 assert!(item.contains("2 3"));
1385 assert!(item.contains("1 2 3"));
1386 assert!(item.contains("4 5 6"));
1387 }
1388 #[test]
1389 fn xdmf_time_element_format() {
1390 let t = xdmf_time_element(1.5);
1391 assert_eq!(t, "<Time Value=\"1.5\"/>");
1392 }
1393 #[test]
1394 fn total_node_count_multi_step() {
1395 let mut ms = XdmfMeshTimeSeries::new();
1396 ms.add_step(XdmfMeshStep {
1397 time: 0.0,
1398 nodes: vec![[0.0; 3]; 4],
1399 connectivity: vec![0, 1, 2],
1400 topology: XdmfTopologyType::Triangle,
1401 node_scalars: vec![],
1402 node_vectors: vec![],
1403 });
1404 ms.add_step(XdmfMeshStep {
1405 time: 1.0,
1406 nodes: vec![[0.0; 3]; 6],
1407 connectivity: vec![0, 1, 2, 3, 4, 5],
1408 topology: XdmfTopologyType::Triangle,
1409 node_scalars: vec![],
1410 node_vectors: vec![],
1411 });
1412 assert_eq!(total_node_count(&ms), 10);
1413 }
1414 #[test]
1415 fn mesh_time_series_to_xml_well_formed() {
1416 let mut ms = XdmfMeshTimeSeries::new();
1417 ms.add_step(XdmfMeshStep {
1418 time: 0.5,
1419 nodes: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
1420 connectivity: vec![0, 1, 2],
1421 topology: XdmfTopologyType::Triangle,
1422 node_scalars: vec![("T".to_string(), vec![300.0, 310.0, 320.0])],
1423 node_vectors: vec![("v".to_string(), vec![[1.0, 0.0, 0.0]; 3])],
1424 });
1425 let xml = ms.to_xml();
1426 assert!(xdmf_is_well_formed(&xml));
1427 assert!(xml.contains("MeshTimeSeries"));
1428 assert!(xml.contains("Time Value=\"0.5\""));
1429 assert!(xml.contains("T"));
1430 assert!(xml.contains("v"));
1431 }
1432}
1433#[cfg(test)]
1434#[allow(dead_code)]
1435mod tests_xdmf_extra {
1436 use super::*;
1437 use crate::xdmf::types::*;
1438 #[test]
1439 fn time_series_default_is_empty() {
1440 let ts: XdmfTimeSeries = Default::default();
1441 assert!(ts.steps.is_empty());
1442 }
1443 #[test]
1444 fn time_series_times_single_step() {
1445 let mut ts = XdmfTimeSeries::new();
1446 ts.add_step(2.5, vec![[0.0; 3]; 3], vec![]);
1447 let t = ts.times();
1448 assert_eq!(t.len(), 1);
1449 assert!((t[0] - 2.5).abs() < 1e-12);
1450 }
1451 #[test]
1452 fn time_series_times_multiple_steps() {
1453 let mut ts = XdmfTimeSeries::new();
1454 ts.add_step(0.0, vec![[0.0; 3]; 2], vec![]);
1455 ts.add_step(1.0, vec![[0.0; 3]; 2], vec![]);
1456 ts.add_step(2.0, vec![[0.0; 3]; 2], vec![]);
1457 let t = ts.times();
1458 assert_eq!(t, vec![0.0, 1.0, 2.0]);
1459 }
1460 #[test]
1461 fn time_series_total_particle_count_empty() {
1462 let ts = XdmfTimeSeries::new();
1463 assert_eq!(ts.total_particle_count(), 0);
1464 }
1465 #[test]
1466 fn time_series_total_particle_count_multi() {
1467 let mut ts = XdmfTimeSeries::new();
1468 ts.add_step(0.0, vec![[0.0; 3]; 4], vec![]);
1469 ts.add_step(1.0, vec![[0.0; 3]; 6], vec![]);
1470 assert_eq!(ts.total_particle_count(), 10);
1471 }
1472 #[test]
1473 fn time_series_add_step_with_vectors() {
1474 let mut ts = XdmfTimeSeries::new();
1475 ts.add_step_with_vectors(
1476 0.1,
1477 vec![[0.0; 3]; 2],
1478 vec![],
1479 vec![("vel".to_string(), vec![[1.0, 0.0, 0.0]; 2])],
1480 );
1481 assert_eq!(ts.steps.len(), 1);
1482 }
1483 #[test]
1484 fn time_series_to_xml_contains_positions() {
1485 let mut ts = XdmfTimeSeries::new();
1486 ts.add_step(0.0, vec![[1.0, 2.0, 3.0]], vec![]);
1487 let xml = ts.to_xml();
1488 assert!(xml.contains("1 2 3") || xml.contains("1.0 2.0 3.0") || xml.contains("1"));
1489 assert!(xml.contains("GeometryType=\"XYZ\""));
1490 }
1491 #[test]
1492 fn time_series_to_xml_contains_scalar_field() {
1493 let mut ts = XdmfTimeSeries::new();
1494 ts.add_step(
1495 0.0,
1496 vec![[0.0; 3]; 2],
1497 vec![("temperature".to_string(), vec![300.0, 301.0])],
1498 );
1499 let xml = ts.to_xml();
1500 assert!(xml.contains("temperature"));
1501 }
1502 #[test]
1503 fn topology_type_triangle_xdmf_name() {
1504 assert_eq!(XdmfTopologyType::Triangle.xdmf_name(), "Triangle");
1505 }
1506 #[test]
1507 fn topology_type_tet_nodes_per_element() {
1508 assert_eq!(XdmfTopologyType::Tetrahedron.nodes_per_element(), 4);
1509 }
1510 #[test]
1511 fn topology_type_quad_nodes_per_element() {
1512 assert_eq!(XdmfTopologyType::Quadrilateral.nodes_per_element(), 4);
1513 }
1514 #[test]
1515 fn topology_type_hex_nodes_per_element() {
1516 assert_eq!(XdmfTopologyType::Hexahedron.nodes_per_element(), 8);
1517 }
1518 #[test]
1519 fn topology_type_mixed_name() {
1520 assert_eq!(XdmfTopologyType::Mixed.xdmf_name(), "Mixed");
1521 }
1522 #[test]
1523 fn mesh_time_series_empty_is_empty() {
1524 let ms = XdmfMeshTimeSeries::new();
1525 assert!(ms.is_empty());
1526 assert_eq!(ms.len(), 0);
1527 }
1528 #[test]
1529 fn mesh_time_series_len_after_add() {
1530 let mut ms = XdmfMeshTimeSeries::new();
1531 ms.add_step(XdmfMeshStep {
1532 time: 0.0,
1533 nodes: vec![[0.0; 3]; 3],
1534 connectivity: vec![0, 1, 2],
1535 topology: XdmfTopologyType::Triangle,
1536 node_scalars: vec![],
1537 node_vectors: vec![],
1538 });
1539 assert_eq!(ms.len(), 1);
1540 assert!(!ms.is_empty());
1541 }
1542 #[test]
1543 fn mesh_time_series_times_list() {
1544 let mut ms = XdmfMeshTimeSeries::new();
1545 for i in 0..3 {
1546 ms.add_step(XdmfMeshStep {
1547 time: i as f64 * 0.5,
1548 nodes: vec![[0.0; 3]; 3],
1549 connectivity: vec![0, 1, 2],
1550 topology: XdmfTopologyType::Triangle,
1551 node_scalars: vec![],
1552 node_vectors: vec![],
1553 });
1554 }
1555 assert_eq!(ms.times(), vec![0.0, 0.5, 1.0]);
1556 }
1557 #[test]
1558 fn mesh_step_n_elements_triangle() {
1559 let step = XdmfMeshStep {
1560 time: 0.0,
1561 nodes: vec![[0.0; 3]; 3],
1562 connectivity: vec![0, 1, 2],
1563 topology: XdmfTopologyType::Triangle,
1564 node_scalars: vec![],
1565 node_vectors: vec![],
1566 };
1567 assert_eq!(step.n_elements(), 1);
1568 }
1569 #[test]
1570 fn mesh_step_n_elements_hex() {
1571 let step = XdmfMeshStep {
1572 time: 0.0,
1573 nodes: vec![[0.0; 3]; 8],
1574 connectivity: vec![0, 1, 2, 3, 4, 5, 6, 7],
1575 topology: XdmfTopologyType::Hexahedron,
1576 node_scalars: vec![],
1577 node_vectors: vec![],
1578 };
1579 assert_eq!(step.n_elements(), 1);
1580 }
1581 #[test]
1582 fn hdf5_builder_no_group() {
1583 let b = Hdf5DataItemBuilder::new("data.h5");
1584 let item = b.build("coordinates", "100 3");
1585 assert!(item.contains("data.h5:/coordinates"));
1586 assert!(item.contains("100 3"));
1587 }
1588 #[test]
1589 fn hdf5_builder_with_group() {
1590 let b = Hdf5DataItemBuilder::new("out.h5").group("step_0");
1591 let item = b.build("positions", "50 3");
1592 assert!(item.contains("step_0") && item.contains("positions"));
1593 assert!(item.contains("out.h5"));
1594 }
1595 #[test]
1596 fn hdf5_builder_dimensions_in_output() {
1597 let b = Hdf5DataItemBuilder::new("f.h5");
1598 let item = b.build("vel", "200 3");
1599 assert!(item.contains("200 3"));
1600 assert!(item.contains("HDF"));
1601 }
1602 #[test]
1603 fn scalar_data_item_empty() {
1604 let item = xdmf_scalar_data_item(&[]);
1605 assert!(item.contains("0"));
1606 }
1607 #[test]
1608 fn scalar_data_item_single_value() {
1609 let item = xdmf_scalar_data_item(&[42.0]);
1610 assert!(item.contains("42"));
1611 assert!(item.contains("Dimensions=\"1\""));
1612 }
1613 #[test]
1614 fn vector_data_item_two_rows() {
1615 let item = xdmf_vector_data_item(&[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
1616 assert!(item.contains("2 3"));
1617 assert!(item.contains("Vector") || item.contains("XML"));
1618 }
1619 #[test]
1620 fn xdmf_schema_validate_ok() {
1621 let schema = XdmfSchema::new("Triangle", vec!["pressure".to_string()]);
1622 let xml = "<Topology TopologyType=\"Triangle\"/><Attribute Name=\"pressure\"/>";
1623 let errs = schema.validate(xml);
1624 assert!(errs.is_empty(), "unexpected errors: {:?}", errs);
1625 }
1626 #[test]
1627 fn xdmf_schema_validate_missing_attr() {
1628 let schema = XdmfSchema::new("Triangle", vec!["velocity".to_string()]);
1629 let xml = "<Topology TopologyType=\"Triangle\"/>";
1630 let errs = schema.validate(xml);
1631 assert!(!errs.is_empty());
1632 }
1633 #[test]
1634 fn xdmf_schema_validate_wrong_topology() {
1635 let schema = XdmfSchema::new("Hexahedron", vec![]);
1636 let xml = "<Topology TopologyType=\"Triangle\"/>";
1637 let errs = schema.validate(xml);
1638 assert!(!errs.is_empty());
1639 }
1640 #[test]
1641 fn xdmf_writer_new_starts_with_xml_declaration() {
1642 let w = XdmfWriter::new();
1643 let out = w.finish();
1644 assert!(out.starts_with("<?xml"));
1645 assert!(out.contains("<Xdmf Version=\"3.0\">"));
1646 }
1647 #[test]
1648 fn xdmf_writer_domain_round_trip() {
1649 let mut w = XdmfWriter::new();
1650 w.open_domain();
1651 w.close_domain();
1652 let out = w.finish();
1653 assert!(out.contains("<Domain>"));
1654 assert!(out.contains("</Domain>"));
1655 }
1656 #[test]
1657 fn xdmf_writer_grid_elements() {
1658 let mut w = XdmfWriter::new();
1659 w.open_domain();
1660 w.open_grid("particles", "Uniform");
1661 w.write_time(3.125);
1662 w.write_polyvertex_topology(100);
1663 w.close_grid();
1664 w.close_domain();
1665 let out = w.finish();
1666 assert!(out.contains("particles"));
1667 assert!(out.contains("3.125"));
1668 assert!(out.contains("100"));
1669 }
1670 #[test]
1671 fn xdmf_writer_temporal_collection() {
1672 let mut w = XdmfWriter::new();
1673 w.open_domain();
1674 w.open_temporal_collection("ts");
1675 w.close_grid();
1676 w.close_domain();
1677 let out = w.finish();
1678 assert!(out.contains("CollectionType=\"Temporal\""));
1679 }
1680 #[test]
1681 fn structured_grid_single_cell_size() {
1682 let g = XdmfStructuredGrid::new("g", 2, 2, 2, [0.0; 3], 1.0, 1.0, 1.0);
1683 assert_eq!(g.n_cells(), 1);
1684 }
1685 #[test]
1686 fn structured_grid_1d_degenerate_cells() {
1687 let g = XdmfStructuredGrid::new("g", 1, 5, 5, [0.0; 3], 1.0, 1.0, 1.0);
1688 assert_eq!(g.n_cells(), 0);
1689 }
1690 #[test]
1691 fn structured_grid_xml_well_formed() {
1692 let g = XdmfStructuredGrid::new("box", 3, 3, 3, [0.0; 3], 0.5, 0.5, 0.5);
1693 let xml = g.to_xml();
1694 assert!(xdmf_is_well_formed(&xml));
1695 }
1696 #[test]
1697 fn total_node_count_empty_series() {
1698 let ms = XdmfMeshTimeSeries::new();
1699 assert_eq!(total_node_count(&ms), 0);
1700 }
1701 #[test]
1702 fn peak_element_step_empty_returns_none() {
1703 let ms = XdmfMeshTimeSeries::new();
1704 assert!(peak_element_step(&ms).is_none());
1705 }
1706 #[test]
1707 fn peak_element_step_single_step() {
1708 let mut ms = XdmfMeshTimeSeries::new();
1709 ms.add_step(XdmfMeshStep {
1710 time: 0.0,
1711 nodes: vec![[0.0; 3]; 4],
1712 connectivity: vec![0, 1, 2, 3],
1713 topology: XdmfTopologyType::Quadrilateral,
1714 node_scalars: vec![],
1715 node_vectors: vec![],
1716 });
1717 assert_eq!(peak_element_step(&ms), Some(0));
1718 }
1719 #[test]
1720 fn hdf5_series_default_empty() {
1721 let s: XdmfTimeSeriesHdf5 = Default::default();
1722 assert!(s.timesteps.is_empty());
1723 assert!(s.hdf5_paths.is_empty());
1724 }
1725 #[test]
1726 fn hdf5_series_stores_attributes() {
1727 let mut s = XdmfTimeSeriesHdf5::new();
1728 s.timesteps.push(0.0);
1729 s.hdf5_paths.push("step0.h5".to_string());
1730 s.attribute_names.push("density".to_string());
1731 assert_eq!(s.attribute_names[0], "density");
1732 }
1733 #[test]
1734 fn write_xdmf_with_attributes_basic() {
1735 let attrs = vec![XdmfAttribute {
1736 name: "pressure".to_string(),
1737 center: "Node".to_string(),
1738 n_components: 1,
1739 hdf5_path: "data.h5:/pressure".to_string(),
1740 }];
1741 let path = "/tmp/test_xdmf_attrs_extra.xmf";
1742 write_xdmf_with_attributes(path, "data.h5", 5, 2, "Triangle", &attrs).unwrap();
1743 let s = std::fs::read_to_string(path).unwrap();
1744 assert!(s.contains("pressure"));
1745 assert!(s.contains("Scalar"));
1746 }
1747 #[test]
1748 fn parse_xdmf_topology_triangle() {
1749 let xml = "<Topology TopologyType=\"Triangle\" NumberOfElements=\"10\"/>";
1750 let result = parse_xdmf_topology(xml);
1751 assert_eq!(result.len(), 1);
1752 assert_eq!(result[0].0, "Triangle");
1753 assert_eq!(result[0].1, 10);
1754 }
1755 #[test]
1756 fn parse_xdmf_topology_multiple() {
1757 let xml = "<Topology TopologyType=\"Triangle\" NumberOfElements=\"4\"/>\n\
1758 <Topology TopologyType=\"Hexahedron\" NumberOfElements=\"2\"/>";
1759 let result = parse_xdmf_topology(xml);
1760 assert_eq!(result.len(), 2);
1761 }
1762 #[test]
1763 fn parse_xdmf_topology_empty_input() {
1764 let result = parse_xdmf_topology("");
1765 assert!(result.is_empty());
1766 }
1767 #[test]
1768 fn xdmf_time_element_zero() {
1769 let t = xdmf_time_element(0.0);
1770 assert_eq!(t, "<Time Value=\"0\"/>");
1771 }
1772 #[test]
1773 fn xdmf_time_element_negative() {
1774 let t = xdmf_time_element(-1.0);
1775 assert!(t.contains("-1"));
1776 }
1777}
1778#[allow(dead_code)]
1782pub fn write_xdmf_timestep_fields<W: std::io::Write>(
1783 writer: &mut W,
1784 time: f64,
1785 n_nodes: usize,
1786 topology_type: &str,
1787 n_elements: usize,
1788 nodes: &[[f64; 3]],
1789 fields: &[XdmfFieldDescriptor],
1790) -> std::io::Result<()> {
1791 writeln!(writer, " <Grid Name=\"timestep\" GridType=\"Uniform\">")?;
1792 writeln!(writer, " <Time Value=\"{time}\"/>")?;
1793 writeln!(
1794 writer,
1795 " <Topology TopologyType=\"{topology_type}\" NumberOfElements=\"{n_elements}\"/>"
1796 )?;
1797 writeln!(writer, " <Geometry GeometryType=\"XYZ\">")?;
1798 writeln!(
1799 writer,
1800 " <DataItem Format=\"XML\" Dimensions=\"{n_nodes} 3\">"
1801 )?;
1802 for n in nodes.iter().take(n_nodes) {
1803 writeln!(writer, " {} {} {}", n[0], n[1], n[2])?;
1804 }
1805 writeln!(writer, " </DataItem>")?;
1806 writeln!(writer, " </Geometry>")?;
1807 for field in fields {
1808 let n_entries = field.entry_count();
1809 let dim_str = if field.n_components == 1 {
1810 format!("{n_entries}")
1811 } else {
1812 format!("{n_entries} {}", field.n_components)
1813 };
1814 writeln!(
1815 writer,
1816 " <Attribute Name=\"{}\" AttributeType=\"{}\" Center=\"{}\">",
1817 field.name, field.attribute_type, field.center
1818 )?;
1819 writeln!(
1820 writer,
1821 " <DataItem Format=\"XML\" Dimensions=\"{dim_str}\">"
1822 )?;
1823 write!(writer, " ")?;
1824 for (i, v) in field.data.iter().enumerate() {
1825 if i > 0 {
1826 write!(writer, " ")?;
1827 }
1828 write!(writer, "{v}")?;
1829 }
1830 writeln!(writer)?;
1831 writeln!(writer, " </DataItem>")?;
1832 writeln!(writer, " </Attribute>")?;
1833 }
1834 writeln!(writer, " </Grid>")?;
1835 Ok(())
1836}
1837#[allow(dead_code)]
1839pub fn patch_element_map(patches: &[XdmfMeshPatch]) -> std::collections::HashMap<usize, String> {
1840 let mut map = std::collections::HashMap::new();
1841 for patch in patches {
1842 for &eid in &patch.element_ids {
1843 map.entry(eid).or_insert_with(|| patch.name.clone());
1844 }
1845 }
1846 map
1847}
1848#[allow(dead_code)]
1850pub fn format_xdmf_geometry_inline(nodes: &[[f64; 3]]) -> String {
1851 nodes
1852 .iter()
1853 .map(|n| format!("{} {} {}", n[0], n[1], n[2]))
1854 .collect::<Vec<_>>()
1855 .join("\n")
1856}
1857#[allow(dead_code)]
1859pub fn format_xdmf_data_inline(data: &[f64]) -> String {
1860 data.iter()
1861 .map(|v| v.to_string())
1862 .collect::<Vec<_>>()
1863 .join(" ")
1864}
1865#[allow(dead_code)]
1870pub fn xdmf_hdf5_dataitem(hdf5_file: &str, dataset: &str, dims: &str, number_type: &str) -> String {
1871 format!(
1872 "<DataItem Format=\"HDF\" Dimensions=\"{dims}\" NumberType=\"{number_type}\" Precision=\"8\">{hdf5_file}:{dataset}</DataItem>"
1873 )
1874}
1875#[allow(dead_code)]
1880pub fn validate_xdmf_structure(xml: &str) -> Result<(), String> {
1881 let required = ["<?xml", "<Xdmf", "<Domain>", "</Domain>", "</Xdmf>"];
1882 for tag in &required {
1883 if !xml.contains(tag) {
1884 return Err(format!("XDMF validation: missing `{tag}`"));
1885 }
1886 }
1887 Ok(())
1888}
1889#[allow(dead_code)]
1891pub fn indent_xdmf(xml: &str, level: usize) -> String {
1892 let prefix = " ".repeat(level * 2);
1893 xml.lines()
1894 .map(|line| format!("{prefix}{line}"))
1895 .collect::<Vec<_>>()
1896 .join("\n")
1897}