1#[allow(unused_imports)]
6use super::functions::*;
7use std::io::Write;
8pub struct XmlVtuWriter;
10impl XmlVtuWriter {
11 pub fn write(grid: &VtkUnstructuredGrid) -> String {
13 let mut s = String::new();
14 s.push_str(&Self::write_xml_header());
15 s.push_str(&format!(
16 " <UnstructuredGrid>\n <Piece NumberOfPoints=\"{}\" NumberOfCells=\"{}\">\n",
17 grid.n_points(),
18 grid.n_cells()
19 ));
20 s.push_str(" <Points>\n");
21 s.push_str(&Self::write_points_xml(&grid.points));
22 s.push_str(" </Points>\n");
23 s.push_str(" <Cells>\n");
24 s.push_str(&Self::write_cells_xml(&grid.cells, &grid.cell_types));
25 s.push_str(" </Cells>\n");
26 if !grid.point_data.is_empty() {
27 s.push_str(" <PointData>\n");
28 for arr in &grid.point_data {
29 match arr {
30 VtkDataArrayW::Scalars { name, values } => {
31 let flat: Vec<f64> = values.clone();
32 s.push_str(&Self::write_data_array_xml(name, 1, &flat));
33 }
34 VtkDataArrayW::Vectors { name, values } => {
35 let flat: Vec<f64> =
36 values.iter().flat_map(|v| v.iter().copied()).collect();
37 s.push_str(&Self::write_data_array_xml(name, 3, &flat));
38 }
39 VtkDataArrayW::Tensors { name, values } => {
40 let flat: Vec<f64> = values
41 .iter()
42 .flat_map(|t| t.iter().flat_map(|r| r.iter().copied()))
43 .collect();
44 s.push_str(&Self::write_data_array_xml(name, 9, &flat));
45 }
46 }
47 }
48 s.push_str(" </PointData>\n");
49 }
50 if !grid.cell_data.is_empty() {
51 s.push_str(" <CellData>\n");
52 for arr in &grid.cell_data {
53 match arr {
54 VtkDataArrayW::Scalars { name, values } => {
55 let flat: Vec<f64> = values.clone();
56 s.push_str(&Self::write_data_array_xml(name, 1, &flat));
57 }
58 VtkDataArrayW::Vectors { name, values } => {
59 let flat: Vec<f64> =
60 values.iter().flat_map(|v| v.iter().copied()).collect();
61 s.push_str(&Self::write_data_array_xml(name, 3, &flat));
62 }
63 VtkDataArrayW::Tensors { name, values } => {
64 let flat: Vec<f64> = values
65 .iter()
66 .flat_map(|t| t.iter().flat_map(|r| r.iter().copied()))
67 .collect();
68 s.push_str(&Self::write_data_array_xml(name, 9, &flat));
69 }
70 }
71 }
72 s.push_str(" </CellData>\n");
73 }
74 s.push_str(" </Piece>\n </UnstructuredGrid>\n</VTKFile>\n");
75 s
76 }
77 pub fn write_xml_header() -> String {
79 "<?xml version=\"1.0\"?>\n<VTKFile type=\"UnstructuredGrid\" version=\"0.1\" byte_order=\"LittleEndian\">\n"
80 .to_owned()
81 }
82 pub fn write_points_xml(points: &[[f64; 3]]) -> String {
84 let mut s = String::from(
85 " <DataArray type=\"Float64\" NumberOfComponents=\"3\" format=\"ascii\">\n",
86 );
87 for p in points {
88 s.push_str(&format!(" {} {} {}\n", p[0], p[1], p[2]));
89 }
90 s.push_str(" </DataArray>\n");
91 s
92 }
93 pub fn write_cells_xml(cells: &[Vec<usize>], cell_types: &[VtkCellTypeW]) -> String {
95 let mut s = String::new();
96 s.push_str(
97 " <DataArray type=\"Int64\" Name=\"connectivity\" format=\"ascii\">\n ",
98 );
99 let mut first = true;
100 for conn in cells {
101 for &idx in conn {
102 if !first {
103 s.push(' ');
104 }
105 s.push_str(&idx.to_string());
106 first = false;
107 }
108 }
109 s.push_str("\n </DataArray>\n");
110 s.push_str(
111 " <DataArray type=\"Int64\" Name=\"offsets\" format=\"ascii\">\n ",
112 );
113 let mut offset: usize = 0;
114 for (i, conn) in cells.iter().enumerate() {
115 if i > 0 {
116 s.push(' ');
117 }
118 offset += conn.len();
119 s.push_str(&offset.to_string());
120 }
121 s.push_str("\n </DataArray>\n");
122 s.push_str(
123 " <DataArray type=\"UInt8\" Name=\"types\" format=\"ascii\">\n ",
124 );
125 for (i, ct) in cell_types.iter().enumerate() {
126 if i > 0 {
127 s.push(' ');
128 }
129 s.push_str(&(*ct as u8).to_string());
130 }
131 s.push_str("\n </DataArray>\n");
132 s
133 }
134 pub fn write_data_array_xml(name: &str, n_components: usize, values: &[f64]) -> String {
136 let mut s = format!(
137 " <DataArray type=\"Float64\" Name=\"{}\" NumberOfComponents=\"{}\" format=\"ascii\">\n ",
138 name, n_components
139 );
140 for (i, v) in values.iter().enumerate() {
141 if i > 0 {
142 s.push(' ');
143 }
144 s.push_str(&v.to_string());
145 }
146 s.push_str("\n </DataArray>\n");
147 s
148 }
149 pub fn encode_base64(data: &[u8]) -> String {
151 const TABLE: &[u8; 64] =
152 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
153 let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
154 for chunk in data.chunks(3) {
155 let b0 = chunk[0] as u32;
156 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
157 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
158 let triple = (b0 << 16) | (b1 << 8) | b2;
159 out.push(TABLE[((triple >> 18) & 0x3F) as usize] as char);
160 out.push(TABLE[((triple >> 12) & 0x3F) as usize] as char);
161 if chunk.len() > 1 {
162 out.push(TABLE[((triple >> 6) & 0x3F) as usize] as char);
163 } else {
164 out.push('=');
165 }
166 if chunk.len() > 2 {
167 out.push(TABLE[(triple & 0x3F) as usize] as char);
168 } else {
169 out.push('=');
170 }
171 }
172 out
173 }
174}
175pub struct LegacyVtkWriter;
177impl LegacyVtkWriter {
178 pub fn write(grid: &VtkUnstructuredGrid) -> String {
180 let mut s = String::new();
181 s.push_str(&Self::write_header());
182 s.push_str(&Self::write_points(&grid.points));
183 s.push_str(&Self::write_cells(&grid.cells, &grid.cell_types));
184 if !grid.point_data.is_empty() {
185 s.push_str(&Self::write_point_data(&grid.point_data));
186 }
187 if !grid.cell_data.is_empty() {
188 s.push_str(&Self::write_cell_data(&grid.cell_data));
189 }
190 s
191 }
192 pub fn write_header() -> String {
194 "# vtk DataFile Version 3.0\nOxiPhysics unstructured grid\nASCII\nDATASET UNSTRUCTURED_GRID\n"
195 .to_owned()
196 }
197 pub fn write_points(points: &[[f64; 3]]) -> String {
199 let mut s = format!("POINTS {} double\n", points.len());
200 for p in points {
201 s.push_str(&format!("{} {} {}\n", p[0], p[1], p[2]));
202 }
203 s
204 }
205 pub fn write_cells(cells: &[Vec<usize>], cell_types: &[VtkCellTypeW]) -> String {
207 let total_entries: usize = cells.iter().map(|c| c.len() + 1).sum();
208 let mut s = format!("CELLS {} {}\n", cells.len(), total_entries);
209 for conn in cells {
210 s.push_str(&conn.len().to_string());
211 for &idx in conn {
212 s.push(' ');
213 s.push_str(&idx.to_string());
214 }
215 s.push('\n');
216 }
217 s.push_str(&format!("CELL_TYPES {}\n", cell_types.len()));
218 for ct in cell_types {
219 s.push_str(&format!("{}\n", *ct as u8));
220 }
221 s
222 }
223 pub fn write_point_data(arrays: &[VtkDataArrayW]) -> String {
225 if arrays.is_empty() {
226 return String::new();
227 }
228 let n = arrays[0].len();
229 let mut s = format!("POINT_DATA {}\n", n);
230 for arr in arrays {
231 s.push_str(&Self::write_data_array(arr));
232 }
233 s
234 }
235 pub fn write_cell_data(arrays: &[VtkDataArrayW]) -> String {
237 if arrays.is_empty() {
238 return String::new();
239 }
240 let n = arrays[0].len();
241 let mut s = format!("CELL_DATA {}\n", n);
242 for arr in arrays {
243 s.push_str(&Self::write_data_array(arr));
244 }
245 s
246 }
247 pub fn write_data_array(arr: &VtkDataArrayW) -> String {
249 let mut s = String::new();
250 match arr {
251 VtkDataArrayW::Scalars { name, values } => {
252 s.push_str(&format!(
253 "SCALARS {} double 1\nLOOKUP_TABLE default\n",
254 name
255 ));
256 for v in values {
257 s.push_str(&format!("{}\n", v));
258 }
259 }
260 VtkDataArrayW::Vectors { name, values } => {
261 s.push_str(&format!("VECTORS {} double\n", name));
262 for v in values {
263 s.push_str(&format!("{} {} {}\n", v[0], v[1], v[2]));
264 }
265 }
266 VtkDataArrayW::Tensors { name, values } => {
267 s.push_str(&format!("TENSORS {} double\n", name));
268 for t in values {
269 for row in t {
270 s.push_str(&format!("{} {} {}\n", row[0], row[1], row[2]));
271 }
272 s.push('\n');
273 }
274 }
275 }
276 s
277 }
278}
279pub struct TimeStepWriter {
281 pub output_dir: String,
283 pub base_name: String,
285 pub entries: Vec<(f64, String)>,
287}
288impl TimeStepWriter {
289 pub fn new(output_dir: impl Into<String>, base_name: impl Into<String>) -> Self {
291 Self {
292 output_dir: output_dir.into(),
293 base_name: base_name.into(),
294 entries: Vec::new(),
295 }
296 }
297 pub fn vtu_filename(&self, step: usize) -> String {
299 format!("{}_{:06}.vtu", self.base_name, step)
300 }
301 pub fn vtu_path(&self, step: usize) -> String {
303 format!("{}/{}", self.output_dir, self.vtu_filename(step))
304 }
305 pub fn register_step(&mut self, time: f64, step: usize) {
307 self.entries.push((time, self.vtu_filename(step)));
308 }
309 pub fn write_pvd(&self, pvd_name: &str) -> std::io::Result<()> {
311 use std::io::Write;
312 let path = format!("{}/{}", self.output_dir, pvd_name);
313 let file = std::fs::File::create(&path)?;
314 let mut w = std::io::BufWriter::new(file);
315 writeln!(w, r#"<?xml version="1.0"?>"#)?;
316 writeln!(
317 w,
318 r#"<VTKFile type="Collection" version="0.1" byte_order="LittleEndian">"#
319 )?;
320 writeln!(w, r#" <Collection>"#)?;
321 for (t, fname) in &self.entries {
322 writeln!(
323 w,
324 r#" <DataSet timestep="{t:.6e}" part="0" file="{fname}"/>"#
325 )?;
326 }
327 writeln!(w, r#" </Collection>"#)?;
328 writeln!(w, r#"</VTKFile>"#)?;
329 w.flush()?;
330 Ok(())
331 }
332 pub fn n_steps(&self) -> usize {
334 self.entries.len()
335 }
336}
337pub struct VtkPolyData {
339 pub points: Vec<[f64; 3]>,
341 pub lines: Vec<Vec<usize>>,
343 pub polygons: Vec<Vec<usize>>,
345 pub point_data: Vec<VtkDataArrayW>,
347}
348impl VtkPolyData {
349 pub fn new() -> Self {
351 Self {
352 points: Vec::new(),
353 lines: Vec::new(),
354 polygons: Vec::new(),
355 point_data: Vec::new(),
356 }
357 }
358 pub fn write_legacy(&self) -> String {
360 let mut s = String::new();
361 s.push_str("# vtk DataFile Version 3.0\nOxiPhysics polydata\nASCII\nDATASET POLYDATA\n");
362 s.push_str(&format!("POINTS {} double\n", self.points.len()));
363 for p in &self.points {
364 s.push_str(&format!("{} {} {}\n", p[0], p[1], p[2]));
365 }
366 if !self.lines.is_empty() {
367 let total: usize = self.lines.iter().map(|l| l.len() + 1).sum();
368 s.push_str(&format!("LINES {} {}\n", self.lines.len(), total));
369 for line in &self.lines {
370 s.push_str(&line.len().to_string());
371 for &idx in line {
372 s.push(' ');
373 s.push_str(&idx.to_string());
374 }
375 s.push('\n');
376 }
377 }
378 if !self.polygons.is_empty() {
379 let total: usize = self.polygons.iter().map(|p| p.len() + 1).sum();
380 s.push_str(&format!("POLYGONS {} {}\n", self.polygons.len(), total));
381 for poly in &self.polygons {
382 s.push_str(&poly.len().to_string());
383 for &idx in poly {
384 s.push(' ');
385 s.push_str(&idx.to_string());
386 }
387 s.push('\n');
388 }
389 }
390 if !self.point_data.is_empty() {
391 s.push_str(&format!("POINT_DATA {}\n", self.points.len()));
392 for arr in &self.point_data {
393 s.push_str(&LegacyVtkWriter::write_data_array(arr));
394 }
395 }
396 s
397 }
398}
399pub struct VtkUnstructuredGrid {
406 pub points: Vec<[f64; 3]>,
408 pub cells: Vec<Vec<usize>>,
410 pub cell_types: Vec<VtkCellTypeW>,
412 pub point_data: Vec<VtkDataArrayW>,
414 pub cell_data: Vec<VtkDataArrayW>,
416}
417impl VtkUnstructuredGrid {
418 pub fn new() -> Self {
420 Self {
421 points: Vec::new(),
422 cells: Vec::new(),
423 cell_types: Vec::new(),
424 point_data: Vec::new(),
425 cell_data: Vec::new(),
426 }
427 }
428 pub fn add_point(&mut self, p: [f64; 3]) -> usize {
430 let idx = self.points.len();
431 self.points.push(p);
432 idx
433 }
434 pub fn add_cell(&mut self, connectivity: Vec<usize>, cell_type: VtkCellTypeW) {
436 self.cells.push(connectivity);
437 self.cell_types.push(cell_type);
438 }
439 pub fn add_point_scalars(&mut self, name: &str, values: Vec<f64>) {
441 self.point_data.push(VtkDataArrayW::Scalars {
442 name: name.to_owned(),
443 values,
444 });
445 }
446 pub fn add_point_vectors(&mut self, name: &str, values: Vec<[f64; 3]>) {
448 self.point_data.push(VtkDataArrayW::Vectors {
449 name: name.to_owned(),
450 values,
451 });
452 }
453 pub fn add_cell_scalars(&mut self, name: &str, values: Vec<f64>) {
455 self.cell_data.push(VtkDataArrayW::Scalars {
456 name: name.to_owned(),
457 values,
458 });
459 }
460 pub fn n_points(&self) -> usize {
462 self.points.len()
463 }
464 pub fn n_cells(&self) -> usize {
466 self.cells.len()
467 }
468}
469#[allow(dead_code)]
471#[derive(Debug, Clone)]
472pub struct VtkPartition {
473 pub rank: usize,
475 pub filename: String,
477 pub point_offset: usize,
479 pub n_points: usize,
481 pub n_cells: usize,
483}
484#[derive(Debug, Clone, Copy, PartialEq, Eq)]
486pub enum VtkCellTypeW {
487 Vertex = 1,
489 Line = 3,
491 Triangle = 5,
493 Quad = 9,
495 Tetrahedron = 10,
497 Hexahedron = 12,
499 Wedge = 13,
501 Pyramid = 14,
503}
504impl VtkCellTypeW {
505 pub fn n_points(&self) -> usize {
507 match self {
508 Self::Vertex => 1,
509 Self::Line => 2,
510 Self::Triangle => 3,
511 Self::Quad => 4,
512 Self::Tetrahedron => 4,
513 Self::Hexahedron => 8,
514 Self::Wedge => 6,
515 Self::Pyramid => 5,
516 }
517 }
518}
519pub struct FormattedAsciiWriter {
521 pub precision: usize,
523}
524impl FormattedAsciiWriter {
525 pub fn new() -> Self {
527 Self { precision: 6 }
528 }
529 pub fn with_precision(precision: usize) -> Self {
531 Self { precision }
532 }
533 pub fn format_f64(&self, v: f64) -> String {
535 format!("{:.prec$}", v, prec = self.precision)
536 }
537 pub fn format_point(&self, p: [f64; 3]) -> String {
539 format!(
540 "{:.prec$} {:.prec$} {:.prec$}",
541 p[0],
542 p[1],
543 p[2],
544 prec = self.precision
545 )
546 }
547 pub fn write_points(&self, points: &[[f64; 3]]) -> String {
549 let mut s = format!("POINTS {} double\n", points.len());
550 for p in points {
551 s.push_str(&self.format_point(*p));
552 s.push('\n');
553 }
554 s
555 }
556 pub fn write_scalars(&self, name: &str, values: &[f64]) -> String {
558 let mut s = format!("SCALARS {} double 1\nLOOKUP_TABLE default\n", name);
559 for v in values {
560 s.push_str(&self.format_f64(*v));
561 s.push('\n');
562 }
563 s
564 }
565 pub fn write_grid(&self, grid: &VtkUnstructuredGrid) -> String {
567 let mut s = LegacyVtkWriter::write_header();
568 s.push_str(&self.write_points(&grid.points));
569 s.push_str(&LegacyVtkWriter::write_cells(&grid.cells, &grid.cell_types));
570 if !grid.point_data.is_empty() {
571 let n = grid.point_data[0].len();
572 s.push_str(&format!("POINT_DATA {}\n", n));
573 for arr in &grid.point_data {
574 if let VtkDataArrayW::Scalars { name, values } = arr {
575 s.push_str(&self.write_scalars(name, values));
576 } else {
577 s.push_str(&LegacyVtkWriter::write_data_array(arr));
578 }
579 }
580 }
581 s
582 }
583}
584#[allow(dead_code)]
586#[derive(Debug, Clone)]
587pub struct VtuValidationResult {
588 pub is_valid: bool,
590 pub issues: Vec<String>,
592}
593impl VtuValidationResult {
594 pub(super) fn ok() -> Self {
595 Self {
596 is_valid: true,
597 issues: Vec::new(),
598 }
599 }
600 pub(super) fn add_issue(&mut self, msg: impl Into<String>) {
601 self.is_valid = false;
602 self.issues.push(msg.into());
603 }
604}
605pub struct VtkCompression;
607impl VtkCompression {
608 pub fn rle_encode_f64(data: &[f64]) -> Vec<(f64, usize)> {
612 if data.is_empty() {
613 return vec![];
614 }
615 let mut runs: Vec<(f64, usize)> = Vec::new();
616 let mut cur = data[0];
617 let mut count = 1usize;
618 for &v in &data[1..] {
619 if (v - cur).abs() < 1e-15 {
620 count += 1;
621 } else {
622 runs.push((cur, count));
623 cur = v;
624 count = 1;
625 }
626 }
627 runs.push((cur, count));
628 runs
629 }
630 pub fn rle_decode_f64(runs: &[(f64, usize)]) -> Vec<f64> {
632 let total: usize = runs.iter().map(|(_, c)| c).sum();
633 let mut out = Vec::with_capacity(total);
634 for &(v, c) in runs {
635 for _ in 0..c {
636 out.push(v);
637 }
638 }
639 out
640 }
641 pub fn compression_ratio(original_len: usize, n_runs: usize) -> f64 {
643 if n_runs == 0 {
644 return 0.0;
645 }
646 original_len as f64 / n_runs as f64
647 }
648 pub fn delta_encode_u8(data: &[u8]) -> (Vec<i8>, u8) {
652 if data.is_empty() {
653 return (vec![], 0);
654 }
655 let first = data[0];
656 let mut deltas: Vec<i8> = Vec::with_capacity(data.len() - 1);
657 for w in data.windows(2) {
658 deltas.push(w[1].wrapping_sub(w[0]) as i8);
659 }
660 (deltas, first)
661 }
662 pub fn delta_decode_u8(deltas: &[i8], first: u8) -> Vec<u8> {
664 let mut out = Vec::with_capacity(deltas.len() + 1);
665 out.push(first);
666 let mut cur = first;
667 for &d in deltas {
668 cur = cur.wrapping_add(d as u8);
669 out.push(cur);
670 }
671 out
672 }
673}
674pub struct ParticleVtkExporter;
676impl ParticleVtkExporter {
677 pub fn export_particles(
681 positions: &[[f64; 3]],
682 velocities: Option<&[[f64; 3]]>,
683 scalars: Option<(&str, &[f64])>,
684 ) -> String {
685 let mut grid = VtkUnstructuredGrid::new();
686 for &p in positions {
687 let idx = grid.add_point(p);
688 grid.add_cell(vec![idx], VtkCellTypeW::Vertex);
689 }
690 if let Some(vels) = velocities {
691 grid.add_point_vectors("velocity", vels.to_vec());
692 }
693 if let Some((name, vals)) = scalars {
694 grid.add_point_scalars(name, vals.to_vec());
695 }
696 LegacyVtkWriter::write(&grid)
697 }
698 pub fn export_sph_particles(
700 positions: &[[f64; 3]],
701 densities: &[f64],
702 pressures: &[f64],
703 ) -> String {
704 let mut grid = VtkUnstructuredGrid::new();
705 for &p in positions {
706 let idx = grid.add_point(p);
707 grid.add_cell(vec![idx], VtkCellTypeW::Vertex);
708 }
709 grid.add_point_scalars("density", densities.to_vec());
710 grid.add_point_scalars("pressure", pressures.to_vec());
711 LegacyVtkWriter::write(&grid)
712 }
713}
714#[derive(Debug, Clone)]
716pub enum VtkDataArrayW {
717 Scalars {
719 name: String,
721 values: Vec<f64>,
723 },
724 Vectors {
726 name: String,
728 values: Vec<[f64; 3]>,
730 },
731 Tensors {
733 name: String,
735 values: Vec<[[f64; 3]; 3]>,
737 },
738}
739impl VtkDataArrayW {
740 pub fn name(&self) -> &str {
742 match self {
743 Self::Scalars { name, .. } => name,
744 Self::Vectors { name, .. } => name,
745 Self::Tensors { name, .. } => name,
746 }
747 }
748 pub fn len(&self) -> usize {
750 match self {
751 Self::Scalars { values, .. } => values.len(),
752 Self::Vectors { values, .. } => values.len(),
753 Self::Tensors { values, .. } => values.len(),
754 }
755 }
756 pub fn is_empty(&self) -> bool {
758 self.len() == 0
759 }
760}
761pub struct VtuXmlWriter;
765impl VtuXmlWriter {
766 #[allow(clippy::too_many_arguments)]
771 pub fn write(
772 path: &str,
773 points: &[[f64; 3]],
774 cells: &[Vec<usize>],
775 cell_types: &[VtkCellTypeW],
776 point_arrays: &[(&str, &[f64])],
777 cell_arrays: &[(&str, &[f64])],
778 ) -> std::io::Result<()> {
779 let file = std::fs::File::create(path)?;
780 let mut w = std::io::BufWriter::new(file);
781 let n_pts = points.len();
782 let n_cells = cells.len();
783 let offsets: Vec<usize> = {
784 let mut off = Vec::with_capacity(n_cells);
785 let mut running = 0_usize;
786 for c in cells {
787 running += c.len();
788 off.push(running);
789 }
790 off
791 };
792 writeln!(w, r#"<?xml version="1.0"?>"#)?;
793 writeln!(
794 w,
795 r#"<VTKFile type="UnstructuredGrid" version="0.1" byte_order="LittleEndian">"#
796 )?;
797 writeln!(w, r#" <UnstructuredGrid>"#)?;
798 writeln!(
799 w,
800 r#" <Piece NumberOfPoints="{n_pts}" NumberOfCells="{n_cells}">"#
801 )?;
802 if !point_arrays.is_empty() {
803 writeln!(w, r#" <PointData>"#)?;
804 for &(name, vals) in point_arrays {
805 writeln!(
806 w,
807 r#" <DataArray type="Float64" Name="{name}" format="ascii">"#
808 )?;
809 let row: Vec<String> = vals.iter().map(|v| format!("{v:.10e}")).collect();
810 writeln!(w, " {}", row.join(" "))?;
811 writeln!(w, r#" </DataArray>"#)?;
812 }
813 writeln!(w, r#" </PointData>"#)?;
814 }
815 if !cell_arrays.is_empty() {
816 writeln!(w, r#" <CellData>"#)?;
817 for &(name, vals) in cell_arrays {
818 writeln!(
819 w,
820 r#" <DataArray type="Float64" Name="{name}" format="ascii">"#
821 )?;
822 let row: Vec<String> = vals.iter().map(|v| format!("{v:.10e}")).collect();
823 writeln!(w, " {}", row.join(" "))?;
824 writeln!(w, r#" </DataArray>"#)?;
825 }
826 writeln!(w, r#" </CellData>"#)?;
827 }
828 writeln!(w, r#" <Points>"#)?;
829 writeln!(
830 w,
831 r#" <DataArray type="Float64" NumberOfComponents="3" format="ascii">"#
832 )?;
833 for p in points {
834 writeln!(w, " {:.10e} {:.10e} {:.10e}", p[0], p[1], p[2])?;
835 }
836 writeln!(w, r#" </DataArray>"#)?;
837 writeln!(w, r#" </Points>"#)?;
838 writeln!(w, r#" <Cells>"#)?;
839 writeln!(
840 w,
841 r#" <DataArray type="Int64" Name="connectivity" format="ascii">"#
842 )?;
843 for c in cells {
844 let row: Vec<String> = c.iter().map(|i| i.to_string()).collect();
845 writeln!(w, " {}", row.join(" "))?;
846 }
847 writeln!(w, r#" </DataArray>"#)?;
848 writeln!(
849 w,
850 r#" <DataArray type="Int64" Name="offsets" format="ascii">"#
851 )?;
852 let off_row: Vec<String> = offsets.iter().map(|o| o.to_string()).collect();
853 writeln!(w, " {}", off_row.join(" "))?;
854 writeln!(w, r#" </DataArray>"#)?;
855 writeln!(
856 w,
857 r#" <DataArray type="UInt8" Name="types" format="ascii">"#
858 )?;
859 let types_row: Vec<String> = cell_types.iter().map(|t| (*t as u8).to_string()).collect();
860 writeln!(w, " {}", types_row.join(" "))?;
861 writeln!(w, r#" </DataArray>"#)?;
862 writeln!(w, r#" </Cells>"#)?;
863 writeln!(w, r#" </Piece>"#)?;
864 writeln!(w, r#" </UnstructuredGrid>"#)?;
865 writeln!(w, r#"</VTKFile>"#)?;
866 w.flush()?;
867 Ok(())
868 }
869}
870pub struct VtkBinaryWriter;
872impl VtkBinaryWriter {
873 pub fn encode_f64_le(values: &[f64]) -> Vec<u8> {
875 let mut out = Vec::with_capacity(values.len() * 8);
876 for &v in values {
877 out.extend_from_slice(&v.to_le_bytes());
878 }
879 out
880 }
881 pub fn encode_i64_le(values: &[i64]) -> Vec<u8> {
883 let mut out = Vec::with_capacity(values.len() * 8);
884 for &v in values {
885 out.extend_from_slice(&v.to_le_bytes());
886 }
887 out
888 }
889 pub fn encode_u8(values: &[u8]) -> Vec<u8> {
891 values.to_vec()
892 }
893 pub fn base64_encode(data: &[u8]) -> String {
895 XmlVtuWriter::encode_base64(data)
896 }
897 pub fn binary_data_array_xml(name: &str, n_components: usize, values: &[f64]) -> String {
901 let raw = Self::encode_f64_le(values);
902 let len_bytes = (raw.len() as u32).to_le_bytes();
903 let mut payload = len_bytes.to_vec();
904 payload.extend_from_slice(&raw);
905 let encoded = Self::base64_encode(&payload);
906 format!(
907 " <DataArray type=\"Float64\" Name=\"{}\" NumberOfComponents=\"{}\" format=\"binary\">\n {}\n </DataArray>\n",
908 name, n_components, encoded
909 )
910 }
911}
912#[allow(dead_code)]
914pub struct VtkWriter;
915#[allow(dead_code)]
916impl VtkWriter {
917 pub fn write_unstructured_vector_field(
927 path: &str,
928 points: &[[f64; 3]],
929 cells: &[Vec<usize>],
930 cell_types: &[VtkCellTypeW],
931 field_name: &str,
932 vectors: &[[f64; 3]],
933 ) -> std::io::Result<()> {
934 let file = std::fs::File::create(path)?;
935 let mut w = std::io::BufWriter::new(file);
936 let n_pts = points.len();
937 let n_cells = cells.len();
938 let offsets: Vec<usize> = {
939 let mut running = 0_usize;
940 cells
941 .iter()
942 .map(|c| {
943 running += c.len();
944 running
945 })
946 .collect()
947 };
948 writeln!(w, r#"<?xml version="1.0"?>"#)?;
949 writeln!(
950 w,
951 r#"<VTKFile type="UnstructuredGrid" version="0.1" byte_order="LittleEndian">"#
952 )?;
953 writeln!(w, r#" <UnstructuredGrid>"#)?;
954 writeln!(
955 w,
956 r#" <Piece NumberOfPoints="{n_pts}" NumberOfCells="{n_cells}">"#
957 )?;
958 if !vectors.is_empty() {
959 writeln!(w, r#" <PointData>"#)?;
960 writeln!(
961 w,
962 r#" <DataArray type="Float64" Name="{field_name}" NumberOfComponents="3" format="ascii">"#
963 )?;
964 for v in vectors {
965 writeln!(w, " {:.10e} {:.10e} {:.10e}", v[0], v[1], v[2])?;
966 }
967 writeln!(w, r#" </DataArray>"#)?;
968 writeln!(w, r#" </PointData>"#)?;
969 }
970 writeln!(w, r#" <Points>"#)?;
971 writeln!(
972 w,
973 r#" <DataArray type="Float64" NumberOfComponents="3" format="ascii">"#
974 )?;
975 for p in points {
976 writeln!(w, " {:.10e} {:.10e} {:.10e}", p[0], p[1], p[2])?;
977 }
978 writeln!(w, r#" </DataArray>"#)?;
979 writeln!(w, r#" </Points>"#)?;
980 writeln!(w, r#" <Cells>"#)?;
981 writeln!(
982 w,
983 r#" <DataArray type="Int64" Name="connectivity" format="ascii">"#
984 )?;
985 for c in cells {
986 let row: Vec<String> = c.iter().map(|i| i.to_string()).collect();
987 writeln!(w, " {}", row.join(" "))?;
988 }
989 writeln!(w, r#" </DataArray>"#)?;
990 writeln!(
991 w,
992 r#" <DataArray type="Int64" Name="offsets" format="ascii">"#
993 )?;
994 let off_row: Vec<String> = offsets.iter().map(|o| o.to_string()).collect();
995 writeln!(w, " {}", off_row.join(" "))?;
996 writeln!(w, r#" </DataArray>"#)?;
997 writeln!(
998 w,
999 r#" <DataArray type="UInt8" Name="types" format="ascii">"#
1000 )?;
1001 let types_row: Vec<String> = cell_types.iter().map(|t| (*t as u8).to_string()).collect();
1002 writeln!(w, " {}", types_row.join(" "))?;
1003 writeln!(w, r#" </DataArray>"#)?;
1004 writeln!(w, r#" </Cells>"#)?;
1005 writeln!(w, r#" </Piece>"#)?;
1006 writeln!(w, r#" </UnstructuredGrid>"#)?;
1007 writeln!(w, r#"</VTKFile>"#)?;
1008 Ok(())
1009 }
1010 pub fn write_streamlines(path: &str, streamlines: &[Vec<[f64; 3]>]) -> std::io::Result<()> {
1019 let mut f = std::fs::File::create(path)?;
1020 let n_points: usize = streamlines.iter().map(|s| s.len()).sum();
1021 let n_lines: usize = streamlines.len();
1022 let total_entries: usize = streamlines.iter().map(|s| s.len() + 1).sum();
1023 writeln!(f, "# vtk DataFile Version 3.0")?;
1024 writeln!(f, "Streamlines")?;
1025 writeln!(f, "ASCII")?;
1026 writeln!(f, "DATASET POLYDATA")?;
1027 writeln!(f, "POINTS {} double", n_points)?;
1028 for sl in streamlines {
1029 for p in sl {
1030 writeln!(f, "{} {} {}", p[0], p[1], p[2])?;
1031 }
1032 }
1033 writeln!(f, "LINES {} {}", n_lines, total_entries)?;
1034 let mut offset = 0usize;
1035 for sl in streamlines {
1036 let n = sl.len();
1037 let indices: Vec<String> = (offset..offset + n).map(|i| i.to_string()).collect();
1038 writeln!(f, "{} {}", n, indices.join(" "))?;
1039 offset += n;
1040 }
1041 Ok(())
1042 }
1043 #[allow(clippy::too_many_arguments)]
1052 pub fn write_rectilinear_grid(
1053 path: &str,
1054 x_coords: &[f64],
1055 y_coords: &[f64],
1056 z_coords: &[f64],
1057 field_name: Option<&str>,
1058 scalars: &[f64],
1059 ) -> std::io::Result<()> {
1060 let mut f = std::fs::File::create(path)?;
1061 let nx = x_coords.len();
1062 let ny = y_coords.len();
1063 let nz = z_coords.len();
1064 let n_pts = nx * ny * nz;
1065 writeln!(f, "# vtk DataFile Version 3.0")?;
1066 writeln!(f, "Rectilinear Grid")?;
1067 writeln!(f, "ASCII")?;
1068 writeln!(f, "DATASET RECTILINEAR_GRID")?;
1069 writeln!(f, "DIMENSIONS {} {} {}", nx, ny, nz)?;
1070 writeln!(f, "X_COORDINATES {} double", nx)?;
1071 for &x in x_coords {
1072 write!(f, "{} ", x)?;
1073 }
1074 writeln!(f)?;
1075 writeln!(f, "Y_COORDINATES {} double", ny)?;
1076 for &y in y_coords {
1077 write!(f, "{} ", y)?;
1078 }
1079 writeln!(f)?;
1080 writeln!(f, "Z_COORDINATES {} double", nz)?;
1081 for &z in z_coords {
1082 write!(f, "{} ", z)?;
1083 }
1084 writeln!(f)?;
1085 if !scalars.is_empty() && scalars.len() == n_pts {
1086 let name = field_name.unwrap_or("scalar_field");
1087 writeln!(f, "POINT_DATA {}", n_pts)?;
1088 writeln!(f, "SCALARS {} double 1", name)?;
1089 writeln!(f, "LOOKUP_TABLE default")?;
1090 for &v in scalars {
1091 writeln!(f, "{}", v)?;
1092 }
1093 }
1094 Ok(())
1095 }
1096}
1097#[allow(dead_code)]
1099#[derive(Debug, Clone)]
1100pub struct PvtuPiece {
1101 pub filename: String,
1103}
1104impl PvtuPiece {
1105 pub fn new(filename: impl Into<String>) -> Self {
1107 Self {
1108 filename: filename.into(),
1109 }
1110 }
1111}
1112pub struct VtkParallelWriter;
1114impl VtkParallelWriter {
1115 pub fn write_pvtu(
1122 partitions: &[VtkPartition],
1123 point_arrays: &[(&str, usize)],
1124 cell_arrays: &[(&str, usize)],
1125 ) -> String {
1126 let mut s = String::new();
1127 s.push_str("<?xml version=\"1.0\"?>\n");
1128 s.push_str(
1129 "<VTKFile type=\"PUnstructuredGrid\" version=\"0.1\" byte_order=\"LittleEndian\">\n",
1130 );
1131 s.push_str(" <PUnstructuredGrid GhostLevel=\"0\">\n");
1132 s.push_str(" <PPoints>\n");
1133 s.push_str(" <PDataArray type=\"Float64\" NumberOfComponents=\"3\"/>\n");
1134 s.push_str(" </PPoints>\n");
1135 if !point_arrays.is_empty() {
1136 s.push_str(" <PPointData>\n");
1137 for (name, ncomp) in point_arrays {
1138 s.push_str(&format!(
1139 " <PDataArray type=\"Float64\" Name=\"{}\" NumberOfComponents=\"{}\"/>\n",
1140 name, ncomp
1141 ));
1142 }
1143 s.push_str(" </PPointData>\n");
1144 }
1145 if !cell_arrays.is_empty() {
1146 s.push_str(" <PCellData>\n");
1147 for (name, ncomp) in cell_arrays {
1148 s.push_str(&format!(
1149 " <PDataArray type=\"Float64\" Name=\"{}\" NumberOfComponents=\"{}\"/>\n",
1150 name, ncomp
1151 ));
1152 }
1153 s.push_str(" </PCellData>\n");
1154 }
1155 for part in partitions {
1156 s.push_str(&format!(" <Piece Source=\"{}\"/>\n", part.filename));
1157 }
1158 s.push_str(" </PUnstructuredGrid>\n</VTKFile>\n");
1159 s
1160 }
1161 pub fn partition_points(n_points: usize, n_ranks: usize) -> Vec<(usize, usize)> {
1165 if n_ranks == 0 || n_points == 0 {
1166 return vec![];
1167 }
1168 let base = n_points / n_ranks;
1169 let extra = n_points % n_ranks;
1170 let mut partitions = Vec::with_capacity(n_ranks);
1171 let mut offset = 0;
1172 for r in 0..n_ranks {
1173 let count = base + if r < extra { 1 } else { 0 };
1174 partitions.push((offset, count));
1175 offset += count;
1176 }
1177 partitions
1178 }
1179}