1use std::fmt::Write as FmtWrite;
28use std::str::SplitAsciiWhitespace;
29
30use crate::Error as IoError;
31
32fn next_f64<'a>(iter: &mut SplitAsciiWhitespace<'a>) -> Result<f64, IoError> {
36 iter.next()
37 .ok_or_else(|| IoError::Parse("unexpected end of PLOT3D data".into()))?
38 .parse::<f64>()
39 .map_err(|e| IoError::Parse(e.to_string()))
40}
41
42fn next_usize<'a>(iter: &mut SplitAsciiWhitespace<'a>) -> Result<usize, IoError> {
44 iter.next()
45 .ok_or_else(|| IoError::Parse("unexpected end of PLOT3D data".into()))?
46 .parse::<usize>()
47 .map_err(|e| IoError::Parse(e.to_string()))
48}
49
50#[allow(dead_code)]
57#[derive(Debug, Clone, PartialEq)]
58pub struct Plot3dBlock {
59 pub ni: usize,
61 pub nj: usize,
63 pub nk: usize,
65 pub x: Vec<f64>,
67 pub y: Vec<f64>,
69 pub z: Vec<f64>,
71}
72
73impl Plot3dBlock {
74 pub fn n_points(&self) -> usize {
76 self.ni * self.nj * self.nk
77 }
78}
79
80#[allow(dead_code)]
82#[derive(Debug, Clone)]
83pub struct Plot3dGrid {
84 pub blocks: Vec<Plot3dBlock>,
86}
87
88impl Plot3dGrid {
89 pub fn new() -> Self {
91 Self { blocks: Vec::new() }
92 }
93
94 pub fn n_blocks(&self) -> usize {
96 self.blocks.len()
97 }
98
99 pub fn block_dimensions(&self, b: usize) -> Option<(usize, usize, usize)> {
103 self.blocks.get(b).map(|blk| (blk.ni, blk.nj, blk.nk))
104 }
105}
106
107impl Default for Plot3dGrid {
108 fn default() -> Self {
109 Self::new()
110 }
111}
112
113pub fn read_plot3d_grid(src: &str) -> Result<Plot3dGrid, IoError> {
118 let mut iter = src.split_ascii_whitespace();
119 let nblocks = next_usize(&mut iter)?;
120 let mut dims: Vec<(usize, usize, usize)> = Vec::with_capacity(nblocks);
121 for _ in 0..nblocks {
122 let ni = next_usize(&mut iter)?;
123 let nj = next_usize(&mut iter)?;
124 let nk = next_usize(&mut iter)?;
125 dims.push((ni, nj, nk));
126 }
127 let mut blocks = Vec::with_capacity(nblocks);
128 for (ni, nj, nk) in dims {
129 let n = ni * nj * nk;
130 let mut x = Vec::with_capacity(n);
131 let mut y = Vec::with_capacity(n);
132 let mut z = Vec::with_capacity(n);
133 for _ in 0..n {
134 x.push(next_f64(&mut iter)?);
135 }
136 for _ in 0..n {
137 y.push(next_f64(&mut iter)?);
138 }
139 for _ in 0..n {
140 z.push(next_f64(&mut iter)?);
141 }
142 blocks.push(Plot3dBlock {
143 ni,
144 nj,
145 nk,
146 x,
147 y,
148 z,
149 });
150 }
151 Ok(Plot3dGrid { blocks })
152}
153
154pub fn write_plot3d_grid(grid: &Plot3dGrid) -> String {
156 let mut out = String::new();
157 let _ = writeln!(out, "{}", grid.n_blocks());
158 for blk in &grid.blocks {
159 let _ = writeln!(out, "{} {} {}", blk.ni, blk.nj, blk.nk);
160 }
161 for blk in &grid.blocks {
162 let n = blk.n_points();
163 for k in 0..n {
164 let _ = writeln!(out, "{:.15e}", blk.x[k]);
165 }
166 for k in 0..n {
167 let _ = writeln!(out, "{:.15e}", blk.y[k]);
168 }
169 for k in 0..n {
170 let _ = writeln!(out, "{:.15e}", blk.z[k]);
171 }
172 }
173 out
174}
175
176#[allow(dead_code)]
180#[derive(Debug, Clone, PartialEq)]
181pub struct Plot3dFreestream {
182 pub mach: f64,
184 pub alpha: f64,
186 pub reynolds: f64,
188 pub time: f64,
190}
191
192#[allow(dead_code)]
197#[derive(Debug, Clone, PartialEq)]
198pub struct Plot3dQPoint {
199 pub rho: f64,
201 pub rho_u: f64,
203 pub rho_v: f64,
205 pub rho_w: f64,
207 pub e: f64,
209}
210
211#[allow(dead_code)]
213#[derive(Debug, Clone)]
214pub struct Plot3dSolutionBlock {
215 pub ni: usize,
217 pub nj: usize,
219 pub nk: usize,
221 pub freestream: Plot3dFreestream,
223 pub q: Vec<Plot3dQPoint>,
225}
226
227impl Plot3dSolutionBlock {
228 pub fn n_points(&self) -> usize {
230 self.ni * self.nj * self.nk
231 }
232}
233
234#[allow(dead_code)]
236#[derive(Debug, Clone)]
237pub struct Plot3dSolution {
238 pub blocks: Vec<Plot3dSolutionBlock>,
240}
241
242impl Plot3dSolution {
243 pub fn n_blocks(&self) -> usize {
245 self.blocks.len()
246 }
247}
248
249pub fn read_plot3d_solution(src: &str) -> Result<Plot3dSolution, IoError> {
254 let mut iter = src.split_ascii_whitespace();
255 let nblocks = next_usize(&mut iter)?;
256 let mut dims: Vec<(usize, usize, usize)> = Vec::with_capacity(nblocks);
257 for _ in 0..nblocks {
258 let ni = next_usize(&mut iter)?;
259 let nj = next_usize(&mut iter)?;
260 let nk = next_usize(&mut iter)?;
261 dims.push((ni, nj, nk));
262 }
263 let mut blocks = Vec::with_capacity(nblocks);
264 for (ni, nj, nk) in dims {
265 let mach = next_f64(&mut iter)?;
266 let alpha = next_f64(&mut iter)?;
267 let reynolds = next_f64(&mut iter)?;
268 let time = next_f64(&mut iter)?;
269 let n = ni * nj * nk;
270 let mut q = Vec::with_capacity(n);
271 for _ in 0..n {
272 let rho = next_f64(&mut iter)?;
273 let rho_u = next_f64(&mut iter)?;
274 let rho_v = next_f64(&mut iter)?;
275 let rho_w = next_f64(&mut iter)?;
276 let e = next_f64(&mut iter)?;
277 q.push(Plot3dQPoint {
278 rho,
279 rho_u,
280 rho_v,
281 rho_w,
282 e,
283 });
284 }
285 blocks.push(Plot3dSolutionBlock {
286 ni,
287 nj,
288 nk,
289 freestream: Plot3dFreestream {
290 mach,
291 alpha,
292 reynolds,
293 time,
294 },
295 q,
296 });
297 }
298 Ok(Plot3dSolution { blocks })
299}
300
301pub fn write_plot3d_solution(sol: &Plot3dSolution) -> String {
303 let mut out = String::new();
304 let _ = writeln!(out, "{}", sol.n_blocks());
305 for blk in &sol.blocks {
306 let _ = writeln!(out, "{} {} {}", blk.ni, blk.nj, blk.nk);
307 }
308 for blk in &sol.blocks {
309 let fs = &blk.freestream;
310 let _ = writeln!(
311 out,
312 "{:.15e} {:.15e} {:.15e} {:.15e}",
313 fs.mach, fs.alpha, fs.reynolds, fs.time
314 );
315 for pt in &blk.q {
316 let _ = writeln!(
317 out,
318 "{:.15e} {:.15e} {:.15e} {:.15e} {:.15e}",
319 pt.rho, pt.rho_u, pt.rho_v, pt.rho_w, pt.e
320 );
321 }
322 }
323 out
324}
325
326#[cfg(test)]
329mod tests {
330 use super::*;
331
332 fn make_single_block_grid() -> Plot3dGrid {
333 let blk = Plot3dBlock {
334 ni: 2,
335 nj: 2,
336 nk: 1,
337 x: vec![0.0, 1.0, 0.0, 1.0],
338 y: vec![0.0, 0.0, 1.0, 1.0],
339 z: vec![0.0, 0.0, 0.0, 0.0],
340 };
341 Plot3dGrid { blocks: vec![blk] }
342 }
343
344 fn make_single_block_solution() -> Plot3dSolution {
345 let fs = Plot3dFreestream {
346 mach: 0.5,
347 alpha: 0.0,
348 reynolds: 1e6,
349 time: 0.0,
350 };
351 let pts: Vec<Plot3dQPoint> = (0..4)
352 .map(|i| Plot3dQPoint {
353 rho: 1.0 + i as f64 * 0.1,
354 rho_u: 0.5,
355 rho_v: 0.0,
356 rho_w: 0.0,
357 e: 2.5,
358 })
359 .collect();
360 Plot3dSolution {
361 blocks: vec![Plot3dSolutionBlock {
362 ni: 2,
363 nj: 2,
364 nk: 1,
365 freestream: fs,
366 q: pts,
367 }],
368 }
369 }
370
371 #[test]
372 fn test_grid_n_blocks() {
373 let g = make_single_block_grid();
374 assert_eq!(g.n_blocks(), 1);
375 }
376
377 #[test]
378 fn test_grid_n_points() {
379 let g = make_single_block_grid();
380 assert_eq!(g.blocks[0].n_points(), 4);
381 }
382
383 #[test]
384 fn test_block_dimensions() {
385 let g = make_single_block_grid();
386 assert_eq!(g.block_dimensions(0), Some((2, 2, 1)));
387 }
388
389 #[test]
390 fn test_block_dimensions_out_of_range() {
391 let g = make_single_block_grid();
392 assert_eq!(g.block_dimensions(5), None);
393 }
394
395 #[test]
396 fn test_write_read_grid_roundtrip() {
397 let original = make_single_block_grid();
398 let s = write_plot3d_grid(&original);
399 let parsed = read_plot3d_grid(&s).expect("parse should succeed");
400 assert_eq!(parsed.n_blocks(), original.n_blocks());
401 let orig_blk = &original.blocks[0];
402 let pars_blk = &parsed.blocks[0];
403 assert_eq!(pars_blk.ni, orig_blk.ni);
404 assert_eq!(pars_blk.nj, orig_blk.nj);
405 assert_eq!(pars_blk.nk, orig_blk.nk);
406 for i in 0..orig_blk.n_points() {
407 assert!((orig_blk.x[i] - pars_blk.x[i]).abs() < 1e-10);
408 assert!((orig_blk.y[i] - pars_blk.y[i]).abs() < 1e-10);
409 assert!((orig_blk.z[i] - pars_blk.z[i]).abs() < 1e-10);
410 }
411 }
412
413 #[test]
414 fn test_read_grid_error_on_empty() {
415 assert!(read_plot3d_grid("").is_err());
416 }
417
418 #[test]
419 fn test_read_grid_error_on_truncated() {
420 assert!(read_plot3d_grid("1").is_err());
422 }
423
424 #[test]
425 fn test_write_grid_contains_nblocks() {
426 let g = make_single_block_grid();
427 let s = write_plot3d_grid(&g);
428 assert!(s.starts_with("1\n"));
429 }
430
431 #[test]
432 fn test_solution_n_blocks() {
433 let sol = make_single_block_solution();
434 assert_eq!(sol.n_blocks(), 1);
435 }
436
437 #[test]
438 fn test_solution_n_points() {
439 let sol = make_single_block_solution();
440 assert_eq!(sol.blocks[0].n_points(), 4);
441 }
442
443 #[test]
444 fn test_write_read_solution_roundtrip() {
445 let original = make_single_block_solution();
446 let s = write_plot3d_solution(&original);
447 let parsed = read_plot3d_solution(&s).expect("parse should succeed");
448 assert_eq!(parsed.n_blocks(), original.n_blocks());
449 let ob = &original.blocks[0];
450 let pb = &parsed.blocks[0];
451 assert!((ob.freestream.mach - pb.freestream.mach).abs() < 1e-10);
452 assert!((ob.freestream.reynolds - pb.freestream.reynolds).abs() < 1.0);
453 for i in 0..ob.n_points() {
454 assert!((ob.q[i].rho - pb.q[i].rho).abs() < 1e-10);
455 assert!((ob.q[i].rho_u - pb.q[i].rho_u).abs() < 1e-10);
456 assert!((ob.q[i].e - pb.q[i].e).abs() < 1e-10);
457 }
458 }
459
460 #[test]
461 fn test_read_solution_error_on_empty() {
462 assert!(read_plot3d_solution("").is_err());
463 }
464
465 #[test]
466 fn test_read_solution_error_on_truncated() {
467 assert!(read_plot3d_solution("1").is_err());
468 }
469
470 #[test]
471 fn test_write_solution_contains_mach() {
472 let sol = make_single_block_solution();
473 let s = write_plot3d_solution(&sol);
474 assert!(s.contains("5.000000000000000e-1") || s.contains("5.0000000000000"));
475 }
476
477 #[test]
478 fn test_multi_block_grid_roundtrip() {
479 let blk1 = Plot3dBlock {
480 ni: 2,
481 nj: 1,
482 nk: 1,
483 x: vec![0.0, 1.0],
484 y: vec![0.0, 0.0],
485 z: vec![0.0, 0.0],
486 };
487 let blk2 = Plot3dBlock {
488 ni: 3,
489 nj: 1,
490 nk: 1,
491 x: vec![1.0, 2.0, 3.0],
492 y: vec![0.0, 0.0, 0.0],
493 z: vec![0.0, 0.0, 0.0],
494 };
495 let grid = Plot3dGrid {
496 blocks: vec![blk1, blk2],
497 };
498 let s = write_plot3d_grid(&grid);
499 let parsed = read_plot3d_grid(&s).unwrap();
500 assert_eq!(parsed.n_blocks(), 2);
501 assert_eq!(parsed.blocks[1].ni, 3);
502 }
503
504 #[test]
505 fn test_plot3d_default_grid() {
506 let g = Plot3dGrid::default();
507 assert_eq!(g.n_blocks(), 0);
508 }
509
510 #[test]
511 fn test_plot3d_block_clone() {
512 let blk = Plot3dBlock {
513 ni: 1,
514 nj: 1,
515 nk: 1,
516 x: vec![0.0],
517 y: vec![0.0],
518 z: vec![0.0],
519 };
520 let blk2 = blk.clone();
521 assert_eq!(blk2.ni, 1);
522 }
523
524 #[test]
525 fn test_freestream_clone() {
526 let fs = Plot3dFreestream {
527 mach: 1.0,
528 alpha: 2.0,
529 reynolds: 3.0,
530 time: 4.0,
531 };
532 let fs2 = fs.clone();
533 assert!((fs2.mach - 1.0).abs() < 1e-12);
534 }
535
536 #[test]
537 fn test_qpoint_clone() {
538 let pt = Plot3dQPoint {
539 rho: 1.0,
540 rho_u: 2.0,
541 rho_v: 3.0,
542 rho_w: 4.0,
543 e: 5.0,
544 };
545 let pt2 = pt.clone();
546 assert!((pt2.e - 5.0).abs() < 1e-12);
547 }
548
549 #[test]
550 fn test_grid_debug_format() {
551 let g = make_single_block_grid();
552 let s = format!("{g:?}");
553 assert!(s.contains("Plot3dGrid"));
554 }
555
556 #[test]
557 fn test_solution_debug_format() {
558 let sol = make_single_block_solution();
559 let s = format!("{sol:?}");
560 assert!(s.contains("Plot3dSolution"));
561 }
562
563 #[test]
564 fn test_write_grid_has_coordinates() {
565 let g = make_single_block_grid();
566 let s = write_plot3d_grid(&g);
567 assert!(s.contains("1.000000000000000e0") || s.contains("1.0000000000000"));
569 }
570
571 #[test]
572 fn test_solution_q_length() {
573 let sol = make_single_block_solution();
574 assert_eq!(sol.blocks[0].q.len(), 4);
575 }
576
577 #[test]
578 fn test_block_dimensions_3d() {
579 let blk = Plot3dBlock {
580 ni: 3,
581 nj: 4,
582 nk: 5,
583 x: vec![0.0; 60],
584 y: vec![0.0; 60],
585 z: vec![0.0; 60],
586 };
587 let g = Plot3dGrid { blocks: vec![blk] };
588 assert_eq!(g.block_dimensions(0), Some((3, 4, 5)));
589 assert_eq!(g.blocks[0].n_points(), 60);
590 }
591
592 #[test]
593 fn test_write_read_3d_block() {
594 let n = 8; let blk = Plot3dBlock {
596 ni: 2,
597 nj: 2,
598 nk: 2,
599 x: (0..n).map(|i| i as f64 * 0.1).collect(),
600 y: (0..n).map(|i| i as f64 * 0.2).collect(),
601 z: (0..n).map(|i| i as f64 * 0.3).collect(),
602 };
603 let grid = Plot3dGrid { blocks: vec![blk] };
604 let s = write_plot3d_grid(&grid);
605 let parsed = read_plot3d_grid(&s).unwrap();
606 for i in 0..n {
607 assert!((grid.blocks[0].z[i] - parsed.blocks[0].z[i]).abs() < 1e-10);
608 }
609 }
610}