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