1#![allow(dead_code)]
13#![allow(non_snake_case)]
14
15use rayon::prelude::*;
16
17#[derive(Debug, Clone)]
25pub struct FluxGrid3D {
26 pub nx: usize,
28 pub ny: usize,
30 pub nz: usize,
32 pub dx: f64,
34 pub values: Vec<f64>,
36}
37
38impl FluxGrid3D {
39 pub fn new(nx: usize, ny: usize, nz: usize, dx: f64, fill: f64) -> Self {
41 Self {
42 nx,
43 ny,
44 nz,
45 dx,
46 values: vec![fill; nx * ny * nz],
47 }
48 }
49
50 #[inline]
52 pub fn idx(&self, i: usize, j: usize, k: usize) -> usize {
53 i * self.ny * self.nz + j * self.nz + k
54 }
55
56 #[inline]
58 pub fn get(&self, i: usize, j: usize, k: usize) -> f64 {
59 self.values[self.idx(i, j, k)]
60 }
61
62 #[inline]
64 pub fn set(&mut self, i: usize, j: usize, k: usize, v: f64) {
65 let idx = self.idx(i, j, k);
66 self.values[idx] = v;
67 }
68
69 pub fn len(&self) -> usize {
71 self.nx * self.ny * self.nz
72 }
73
74 pub fn is_empty(&self) -> bool {
76 self.len() == 0
77 }
78
79 pub fn get_clamped(&self, i: isize, j: isize, k: isize) -> f64 {
82 let ci = i.clamp(0, self.nx as isize - 1) as usize;
83 let cj = j.clamp(0, self.ny as isize - 1) as usize;
84 let ck = k.clamp(0, self.nz as isize - 1) as usize;
85 self.get(ci, cj, ck)
86 }
87
88 pub fn l2_norm(&self) -> f64 {
90 let sum_sq: f64 = self.values.iter().map(|&v| v * v).sum();
91 sum_sq.sqrt()
92 }
93
94 pub fn max_val(&self) -> f64 {
96 self.values
97 .iter()
98 .copied()
99 .fold(f64::NEG_INFINITY, f64::max)
100 }
101
102 pub fn min_val(&self) -> f64 {
104 self.values.iter().copied().fold(f64::INFINITY, f64::min)
105 }
106}
107
108#[derive(Debug, Clone)]
117pub struct VectorFluxGrid3D {
118 pub u: FluxGrid3D,
120 pub v: FluxGrid3D,
122 pub w: FluxGrid3D,
124}
125
126impl VectorFluxGrid3D {
127 pub fn zeros(nx: usize, ny: usize, nz: usize, dx: f64) -> Self {
129 Self {
130 u: FluxGrid3D::new(nx, ny, nz, dx, 0.0),
131 v: FluxGrid3D::new(nx, ny, nz, dx, 0.0),
132 w: FluxGrid3D::new(nx, ny, nz, dx, 0.0),
133 }
134 }
135
136 pub fn set_vel(&mut self, i: usize, j: usize, k: usize, vel: [f64; 3]) {
138 self.u.set(i, j, k, vel[0]);
139 self.v.set(i, j, k, vel[1]);
140 self.w.set(i, j, k, vel[2]);
141 }
142
143 pub fn get_vel(&self, i: usize, j: usize, k: usize) -> [f64; 3] {
145 [
146 self.u.get(i, j, k),
147 self.v.get(i, j, k),
148 self.w.get(i, j, k),
149 ]
150 }
151}
152
153#[inline]
161pub fn minmod(a: f64, b: f64) -> f64 {
162 if a * b <= 0.0 {
163 0.0
164 } else if a.abs() < b.abs() {
165 a
166 } else {
167 b
168 }
169}
170
171pub fn superbee(a: f64, b: f64) -> f64 {
173 if a * b <= 0.0 {
174 return 0.0;
175 }
176 let s = if a >= 0.0 { 1.0 } else { -1.0 };
177 s * a.abs().max(b.abs()).min(2.0 * a.abs().min(b.abs()))
178}
179
180pub fn van_leer(a: f64, b: f64) -> f64 {
182 if a * b <= 0.0 {
183 return 0.0;
184 }
185 2.0 * a * b / (a + b)
186}
187
188pub fn mc_limiter(a: f64, b: f64) -> f64 {
190 if a * b <= 0.0 {
191 return 0.0;
192 }
193 let centered = 0.5 * (a + b);
194 let s = if a >= 0.0 { 1.0 } else { -1.0 };
195 s * centered.abs().min(2.0 * a.abs()).min(2.0 * b.abs())
196}
197
198pub fn upwind_flux_1d(phi: &[f64], vel: f64, dx: f64, dt: f64) -> Vec<f64> {
209 let n = phi.len();
210 let mut flux = vec![0.0; n];
211 let cfl = vel * dt / dx;
212 for i in 0..n {
213 let phi_l = if i == 0 { phi[0] } else { phi[i - 1] };
214 let phi_r = if i + 1 >= n { phi[n - 1] } else { phi[i + 1] };
215 if vel >= 0.0 {
216 flux[i] = -cfl * (phi[i] - phi_l);
218 } else {
219 flux[i] = -cfl * (phi_r - phi[i]);
221 }
222 }
223 flux
224}
225
226pub fn advect_upwind_3d(phi: &FluxGrid3D, vel: &VectorFluxGrid3D, dt: f64) -> FluxGrid3D {
230 let _nx = phi.nx;
231 let ny = phi.ny;
232 let nz = phi.nz;
233 let dx = phi.dx;
234
235 let mut out = phi.clone();
236
237 out.values.par_iter_mut().enumerate().for_each(|(idx, v)| {
238 let i = idx / (ny * nz);
239 let j = (idx / nz) % ny;
240 let k = idx % nz;
241
242 let ux = vel.u.get(i, j, k);
243 let uy = vel.v.get(i, j, k);
244 let uz = vel.w.get(i, j, k);
245
246 let phi_xm = phi.get_clamped(i as isize - 1, j as isize, k as isize);
247 let phi_xp = phi.get_clamped(i as isize + 1, j as isize, k as isize);
248 let phi_ym = phi.get_clamped(i as isize, j as isize - 1, k as isize);
249 let phi_yp = phi.get_clamped(i as isize, j as isize + 1, k as isize);
250 let phi_zm = phi.get_clamped(i as isize, j as isize, k as isize - 1);
251 let phi_zp = phi.get_clamped(i as isize, j as isize, k as isize + 1);
252 let phi_c = phi.get(i, j, k);
253
254 let flux_x = if ux >= 0.0 {
255 ux * (phi_c - phi_xm) / dx
256 } else {
257 ux * (phi_xp - phi_c) / dx
258 };
259 let flux_y = if uy >= 0.0 {
260 uy * (phi_c - phi_ym) / dx
261 } else {
262 uy * (phi_yp - phi_c) / dx
263 };
264 let flux_z = if uz >= 0.0 {
265 uz * (phi_c - phi_zm) / dx
266 } else {
267 uz * (phi_zp - phi_c) / dx
268 };
269
270 *v = phi_c - dt * (flux_x + flux_y + flux_z);
271 });
272
273 out
274}
275
276#[inline]
284pub fn lax_friedrichs_flux(u_l: f64, u_r: f64, f_l: f64, f_r: f64, alpha: f64) -> f64 {
285 0.5 * (f_l + f_r) - 0.5 * alpha * (u_r - u_l)
286}
287
288pub fn lax_friedrichs_advect_1d(u: &[f64], vel: f64, dx: f64, dt: f64) -> Vec<f64> {
293 let n = u.len();
294 if n == 0 {
295 return Vec::new();
296 }
297 let alpha = vel.abs();
298 let mut u_new = vec![0.0; n];
299 for i in 0..n {
300 let u_l = if i == 0 { u[0] } else { u[i - 1] };
301 let u_r = if i + 1 >= n { u[n - 1] } else { u[i + 1] };
302 let f_l = vel * u_l;
303 let f_r = vel * u_r;
304 let f_left = lax_friedrichs_flux(u_l, u[i], f_l, vel * u[i], alpha);
305 let f_right = lax_friedrichs_flux(u[i], u_r, vel * u[i], f_r, alpha);
306 u_new[i] = u[i] - dt / dx * (f_right - f_left);
307 }
308 u_new
309}
310
311pub fn divergence_3d(vel: &VectorFluxGrid3D) -> FluxGrid3D {
319 let nx = vel.u.nx;
320 let ny = vel.u.ny;
321 let nz = vel.u.nz;
322 let dx = vel.u.dx;
323
324 let mut div = FluxGrid3D::new(nx, ny, nz, dx, 0.0);
325 div.values.par_iter_mut().enumerate().for_each(|(idx, d)| {
326 let i = idx / (ny * nz);
327 let j = (idx / nz) % ny;
328 let k = idx % nz;
329 let ii = i as isize;
330 let jj = j as isize;
331 let kk = k as isize;
332 let du_dx =
333 (vel.u.get_clamped(ii + 1, jj, kk) - vel.u.get_clamped(ii - 1, jj, kk)) / (2.0 * dx);
334 let dv_dy =
335 (vel.v.get_clamped(ii, jj + 1, kk) - vel.v.get_clamped(ii, jj - 1, kk)) / (2.0 * dx);
336 let dw_dz =
337 (vel.w.get_clamped(ii, jj, kk + 1) - vel.w.get_clamped(ii, jj, kk - 1)) / (2.0 * dx);
338 *d = du_dx + dv_dy + dw_dz;
339 });
340 div
341}
342
343pub fn gradient_3d(phi: &FluxGrid3D) -> VectorFluxGrid3D {
347 let nx = phi.nx;
348 let ny = phi.ny;
349 let nz = phi.nz;
350 let dx = phi.dx;
351
352 let mut grad = VectorFluxGrid3D::zeros(nx, ny, nz, dx);
353
354 let u_vals: Vec<f64> = (0..nx * ny * nz)
355 .into_par_iter()
356 .map(|idx| {
357 let i = idx / (ny * nz);
358 let j = (idx / nz) % ny;
359 let k = idx % nz;
360 let ii = i as isize;
361 let jj = j as isize;
362 let kk = k as isize;
363 (phi.get_clamped(ii + 1, jj, kk) - phi.get_clamped(ii - 1, jj, kk)) / (2.0 * dx)
364 })
365 .collect();
366
367 let v_vals: Vec<f64> = (0..nx * ny * nz)
368 .into_par_iter()
369 .map(|idx| {
370 let i = idx / (ny * nz);
371 let j = (idx / nz) % ny;
372 let k = idx % nz;
373 let ii = i as isize;
374 let jj = j as isize;
375 let kk = k as isize;
376 (phi.get_clamped(ii, jj + 1, kk) - phi.get_clamped(ii, jj - 1, kk)) / (2.0 * dx)
377 })
378 .collect();
379
380 let w_vals: Vec<f64> = (0..nx * ny * nz)
381 .into_par_iter()
382 .map(|idx| {
383 let i = idx / (ny * nz);
384 let j = (idx / nz) % ny;
385 let k = idx % nz;
386 let ii = i as isize;
387 let jj = j as isize;
388 let kk = k as isize;
389 (phi.get_clamped(ii, jj, kk + 1) - phi.get_clamped(ii, jj, kk - 1)) / (2.0 * dx)
390 })
391 .collect();
392
393 grad.u.values = u_vals;
394 grad.v.values = v_vals;
395 grad.w.values = w_vals;
396 grad
397}
398
399pub fn euler_step_advect(phi: &FluxGrid3D, vel: &VectorFluxGrid3D, dt: f64) -> FluxGrid3D {
407 advect_upwind_3d(phi, vel, dt)
408}
409
410pub fn cfl_dt(vel: &VectorFluxGrid3D, cfl_factor: f64) -> f64 {
414 let max_u = vel
415 .u
416 .values
417 .iter()
418 .copied()
419 .map(f64::abs)
420 .fold(0.0_f64, f64::max);
421 let max_v = vel
422 .v
423 .values
424 .iter()
425 .copied()
426 .map(f64::abs)
427 .fold(0.0_f64, f64::max);
428 let max_w = vel
429 .w
430 .values
431 .iter()
432 .copied()
433 .map(f64::abs)
434 .fold(0.0_f64, f64::max);
435 let max_vel = max_u.max(max_v).max(max_w);
436 let dx = vel.u.dx;
437 if max_vel < 1e-300 {
438 f64::INFINITY
439 } else {
440 cfl_factor * dx / max_vel
441 }
442}
443
444#[allow(dead_code)]
450#[derive(Debug, Clone, Copy)]
451pub struct EulerState {
452 pub rho: f64,
454 pub rho_u: f64,
456 pub rho_v: f64,
458 pub rho_w: f64,
460 pub e: f64,
462}
463
464impl EulerState {
465 #[allow(dead_code)]
467 pub fn from_primitives(rho: f64, u: f64, v: f64, w: f64, p: f64, gamma: f64) -> Self {
468 let ke = 0.5 * rho * (u * u + v * v + w * w);
469 let e = p / (gamma - 1.0) + ke;
470 Self {
471 rho,
472 rho_u: rho * u,
473 rho_v: rho * v,
474 rho_w: rho * w,
475 e,
476 }
477 }
478
479 #[allow(dead_code)]
481 pub fn velocity(&self) -> [f64; 3] {
482 let rho = if self.rho.abs() > 1e-30 {
483 self.rho
484 } else {
485 1e-30
486 };
487 [self.rho_u / rho, self.rho_v / rho, self.rho_w / rho]
488 }
489
490 #[allow(dead_code)]
492 pub fn pressure(&self, gamma: f64) -> f64 {
493 let u = self.velocity();
494 let ke = 0.5 * self.rho * (u[0] * u[0] + u[1] * u[1] + u[2] * u[2]);
495 (gamma - 1.0) * (self.e - ke)
496 }
497
498 #[allow(dead_code)]
500 pub fn sound_speed(&self, gamma: f64) -> f64 {
501 let p = self.pressure(gamma);
502 let rho = if self.rho.abs() > 1e-30 {
503 self.rho
504 } else {
505 1e-30
506 };
507 (gamma * p / rho).sqrt().max(0.0)
508 }
509
510 #[allow(dead_code)]
512 pub fn flux_x(&self, gamma: f64) -> [f64; 5] {
513 let u = self.velocity();
514 let p = self.pressure(gamma);
515 [
516 self.rho_u,
517 self.rho_u * u[0] + p,
518 self.rho_v * u[0],
519 self.rho_w * u[0],
520 (self.e + p) * u[0],
521 ]
522 }
523
524 #[allow(dead_code)]
526 pub fn lerp(&self, other: &EulerState, t: f64) -> EulerState {
527 EulerState {
528 rho: self.rho + t * (other.rho - self.rho),
529 rho_u: self.rho_u + t * (other.rho_u - self.rho_u),
530 rho_v: self.rho_v + t * (other.rho_v - self.rho_v),
531 rho_w: self.rho_w + t * (other.rho_w - self.rho_w),
532 e: self.e + t * (other.e - self.e),
533 }
534 }
535}
536
537#[allow(dead_code)]
545pub fn van_albada(a: f64, b: f64) -> f64 {
546 if a * b <= 0.0 {
547 return 0.0;
548 }
549 let r = a / b;
550 b * (r * r + r) / (r * r + 1.0)
551}
552
553#[allow(dead_code)]
566pub fn muscl_reconstruct(u: &[f64], i: usize, limiter: fn(f64, f64) -> f64) -> (f64, f64) {
567 let n = u.len();
568 let u_im = if i == 0 { u[0] } else { u[i - 1] };
569 let u_i = u[i];
570 let u_ip = if i + 1 < n { u[i + 1] } else { u[n - 1] };
571 let u_ipp = if i + 2 < n { u[i + 2] } else { u[n - 1] };
572
573 let delta_m = u_i - u_im;
574 let delta_p = u_ip - u_i;
575 let delta_pp = u_ipp - u_ip;
576
577 let sigma_l = limiter(delta_m, delta_p);
578 let sigma_r = limiter(delta_p, delta_pp);
579
580 let u_l = u_i + 0.5 * sigma_l;
581 let u_r = u_ip - 0.5 * sigma_r;
582 (u_l, u_r)
583}
584
585#[allow(dead_code)]
589pub fn muscl_reconstruct_all(u: &[f64], limiter: fn(f64, f64) -> f64) -> Vec<(f64, f64)> {
590 let n = u.len();
591 if n < 2 {
592 return Vec::new();
593 }
594 (0..n - 1)
595 .map(|i| muscl_reconstruct(u, i, limiter))
596 .collect()
597}
598
599#[allow(dead_code)]
607pub fn godunov_flux_advection(u_l: f64, u_r: f64, wave_speed: f64) -> f64 {
608 if wave_speed >= 0.0 {
609 wave_speed * u_l
610 } else {
611 wave_speed * u_r
612 }
613}
614
615#[allow(dead_code)]
619pub fn godunov_flux_burgers(u_l: f64, u_r: f64) -> f64 {
620 if u_l >= u_r {
623 let s = 0.5 * (u_l + u_r);
625 if s >= 0.0 {
626 0.5 * u_l * u_l
627 } else {
628 0.5 * u_r * u_r
629 }
630 } else {
631 if u_l >= 0.0 {
633 0.5 * u_l * u_l
634 } else if u_r <= 0.0 {
635 0.5 * u_r * u_r
636 } else {
637 0.0 }
639 }
640}
641
642#[allow(dead_code)]
651pub fn roe_flux_scalar(u_l: f64, u_r: f64, a_roe: f64) -> f64 {
652 let f_l = a_roe * u_l;
653 let f_r = a_roe * u_r;
654 0.5 * (f_l + f_r) - 0.5 * a_roe.abs() * (u_r - u_l)
655}
656
657#[allow(dead_code)]
662pub fn roe_flux_euler_1d(
663 rho_l: f64,
664 u_l: f64,
665 p_l: f64,
666 rho_r: f64,
667 u_r: f64,
668 p_r: f64,
669 gamma: f64,
670) -> [f64; 3] {
671 let sqrt_rl = rho_l.sqrt();
673 let sqrt_rr = rho_r.sqrt();
674 let denom = sqrt_rl + sqrt_rr;
675
676 let u_roe = (sqrt_rl * u_l + sqrt_rr * u_r) / denom;
677 let h_l = (p_l / (rho_l * (gamma - 1.0)) + p_l / rho_l) + 0.5 * u_l * u_l;
678 let h_r = (p_r / (rho_r * (gamma - 1.0)) + p_r / rho_r) + 0.5 * u_r * u_r;
679 let h_roe = (sqrt_rl * h_l + sqrt_rr * h_r) / denom;
680
681 let a2 = (gamma - 1.0) * (h_roe - 0.5 * u_roe * u_roe);
682 let a_roe = if a2 > 0.0 { a2.sqrt() } else { 0.0 };
683
684 let e_l = p_l / (gamma - 1.0) + 0.5 * rho_l * u_l * u_l;
686 let e_r = p_r / (gamma - 1.0) + 0.5 * rho_r * u_r * u_r;
687
688 let fl = [rho_l * u_l, rho_l * u_l * u_l + p_l, (e_l + p_l) * u_l];
690 let fr = [rho_r * u_r, rho_r * u_r * u_r + p_r, (e_r + p_r) * u_r];
691
692 let du = [rho_r - rho_l, rho_r * u_r - rho_l * u_l, e_r - e_l];
694
695 let lam = [u_roe - a_roe, u_roe, u_roe + a_roe];
697
698 let r_inv_du = [
700 (du[0] * u_roe - du[1]) / (2.0 * a_roe.max(1e-30)),
701 du[0] - (du[0] * u_roe - du[1]) / (a_roe.max(1e-30)),
702 (du[0] * u_roe + du[1]) / (2.0 * a_roe.max(1e-30)),
703 ];
704
705 let r_mat = [
707 [1.0, 1.0, 1.0],
708 [u_roe - a_roe, u_roe, u_roe + a_roe],
709 [
710 h_roe - u_roe * a_roe,
711 0.5 * u_roe * u_roe,
712 h_roe + u_roe * a_roe,
713 ],
714 ];
715
716 let mut dissipation = [0.0f64; 3];
717 for i in 0..3 {
718 for k in 0..3 {
719 dissipation[i] += lam[k].abs() * r_inv_du[k] * r_mat[i][k];
720 }
721 }
722
723 [
724 0.5 * (fl[0] + fr[0]) - 0.5 * dissipation[0],
725 0.5 * (fl[1] + fr[1]) - 0.5 * dissipation[1],
726 0.5 * (fl[2] + fr[2]) - 0.5 * dissipation[2],
727 ]
728}
729
730#[allow(dead_code)]
739pub fn hll_flux(u_l: f64, u_r: f64, f_l: f64, f_r: f64, s_l: f64, s_r: f64) -> f64 {
740 if s_l >= 0.0 {
741 f_l
742 } else if s_r <= 0.0 {
743 f_r
744 } else {
745 (s_r * f_l - s_l * f_r + s_l * s_r * (u_r - u_l)) / (s_r - s_l)
746 }
747}
748
749#[allow(dead_code)]
753pub fn hll_wave_speeds(u_l: f64, a_l: f64, u_r: f64, a_r: f64) -> (f64, f64) {
754 let s_l = (u_l - a_l).min(u_r - a_r);
755 let s_r = (u_l + a_l).max(u_r + a_r);
756 (s_l, s_r)
757}
758
759#[allow(dead_code)]
763pub fn hll_flux_euler_1d(
764 rho_l: f64,
765 u_l: f64,
766 p_l: f64,
767 rho_r: f64,
768 u_r: f64,
769 p_r: f64,
770 gamma: f64,
771) -> [f64; 3] {
772 let a_l = (gamma * p_l / rho_l).sqrt().max(0.0);
773 let a_r = (gamma * p_r / rho_r).sqrt().max(0.0);
774 let (s_l, s_r) = hll_wave_speeds(u_l, a_l, u_r, a_r);
775
776 let e_l = p_l / (gamma - 1.0) + 0.5 * rho_l * u_l * u_l;
777 let e_r = p_r / (gamma - 1.0) + 0.5 * rho_r * u_r * u_r;
778
779 let fl = [rho_l * u_l, rho_l * u_l * u_l + p_l, (e_l + p_l) * u_l];
780 let fr = [rho_r * u_r, rho_r * u_r * u_r + p_r, (e_r + p_r) * u_r];
781 let ul = [rho_l, rho_l * u_l, e_l];
782 let ur = [rho_r, rho_r * u_r, e_r];
783
784 let mut f = [0.0f64; 3];
785 for i in 0..3 {
786 f[i] = hll_flux(ul[i], ur[i], fl[i], fr[i], s_l, s_r);
787 }
788 f
789}
790
791#[allow(dead_code)]
800pub fn hllc_flux_euler_1d(
801 rho_l: f64,
802 u_l: f64,
803 p_l: f64,
804 rho_r: f64,
805 u_r: f64,
806 p_r: f64,
807 gamma: f64,
808) -> [f64; 3] {
809 let a_l = (gamma * p_l / rho_l.max(1e-30)).sqrt();
810 let a_r = (gamma * p_r / rho_r.max(1e-30)).sqrt();
811
812 let (s_l, s_r) = hll_wave_speeds(u_l, a_l, u_r, a_r);
814
815 let s_star = (p_r - p_l + rho_l * u_l * (s_l - u_l) - rho_r * u_r * (s_r - u_r))
817 / (rho_l * (s_l - u_l) - rho_r * (s_r - u_r) + 1e-200);
818
819 let e_l = p_l / (gamma - 1.0) + 0.5 * rho_l * u_l * u_l;
820 let e_r = p_r / (gamma - 1.0) + 0.5 * rho_r * u_r * u_r;
821
822 let fl = [rho_l * u_l, rho_l * u_l * u_l + p_l, (e_l + p_l) * u_l];
823 let fr = [rho_r * u_r, rho_r * u_r * u_r + p_r, (e_r + p_r) * u_r];
824
825 let hllc_flux_side = |rho: f64, u: f64, e: f64, p: f64, f: &[f64; 3], s: f64| -> [f64; 3] {
826 let coeff = rho * (s - u) / (s - s_star + 1e-200);
827 let u_star = [
828 coeff,
829 coeff * s_star,
830 coeff * (e / rho + (s_star - u) * (s_star + p / (rho * (s - u) + 1e-200))),
831 ];
832 [
833 f[0] + s * (u_star[0] - rho),
834 f[1] + s * (u_star[1] - rho * u),
835 f[2] + s * (u_star[2] - e),
836 ]
837 };
838
839 if s_l >= 0.0 {
840 fl
841 } else if s_star >= 0.0 {
842 hllc_flux_side(rho_l, u_l, e_l, p_l, &fl, s_l)
843 } else if s_r >= 0.0 {
844 hllc_flux_side(rho_r, u_r, e_r, p_r, &fr, s_r)
845 } else {
846 fr
847 }
848}
849
850#[allow(dead_code)]
860pub fn characteristic_decompose_2wave(u0: f64, u1: f64, a: f64) -> (f64, f64) {
861 let w_plus = 0.5 * (u0 + u1 / a);
862 let w_minus = 0.5 * (u0 - u1 / a);
863 (w_plus, w_minus)
864}
865
866#[allow(dead_code)]
868pub fn characteristic_recompose_2wave(w_plus: f64, w_minus: f64, a: f64) -> (f64, f64) {
869 let u0 = w_plus + w_minus;
870 let u1 = a * (w_plus - w_minus);
871 (u0, u1)
872}
873
874#[allow(dead_code)]
880pub fn characteristic_limited_reconstruct(
881 u0: &[f64],
882 u1: &[f64],
883 i: usize,
884 a: f64,
885 limiter: fn(f64, f64) -> f64,
886) -> ([f64; 2], [f64; 2]) {
887 let n = u0.len();
888 let get = |arr: &[f64], j: usize| -> f64 { arr[j.min(n - 1)] };
889
890 let (wm_im, _) = characteristic_decompose_2wave(
892 get(u0, i.saturating_sub(1)),
893 get(u1, i.saturating_sub(1)),
894 a,
895 );
896 let (wm_i, wp_i) = characteristic_decompose_2wave(get(u0, i), get(u1, i), a);
897 let (wm_ip, wp_ip) = characteristic_decompose_2wave(get(u0, i + 1), get(u1, i + 1), a);
898 let (wm_ipp, wp_ipp) = if i + 2 < n {
899 characteristic_decompose_2wave(get(u0, i + 2), get(u1, i + 2), a)
900 } else {
901 (wm_ip, wp_ip)
902 };
903 let (_, wp_im) = characteristic_decompose_2wave(
904 get(u0, i.saturating_sub(1)),
905 get(u1, i.saturating_sub(1)),
906 a,
907 );
908
909 let sig_p_l = limiter(wp_i - wp_im, wp_ip - wp_i);
911 let sig_p_r = limiter(wp_ip - wp_i, wp_ipp - wp_ip);
912 let sig_m_l = limiter(wm_i - wm_im, wm_ip - wm_i);
913 let sig_m_r = limiter(wm_ip - wm_i, wm_ipp - wm_ip);
914
915 let wp_l = wp_i + 0.5 * sig_p_l;
916 let wp_r = wp_ip - 0.5 * sig_p_r;
917 let wm_l = wm_i + 0.5 * sig_m_l;
918 let wm_r = wm_ip - 0.5 * sig_m_r;
919
920 let (u0_l, u1_l) = characteristic_recompose_2wave(wp_l, wm_l, a);
921 let (u0_r, u1_r) = characteristic_recompose_2wave(wp_r, wm_r, a);
922
923 ([u0_l, u1_l], [u0_r, u1_r])
924}
925
926#[allow(dead_code)]
938pub fn tvd_rk2_advect(phi: &FluxGrid3D, vel: &VectorFluxGrid3D, dt: f64) -> FluxGrid3D {
939 let phi_1 = advect_upwind_3d(phi, vel, dt);
941 let phi_2 = advect_upwind_3d(&phi_1, vel, dt);
943 let mut result = phi.clone();
945 result
946 .values
947 .iter_mut()
948 .zip(phi.values.iter().zip(phi_2.values.iter()))
949 .for_each(|(r, (&a, &b))| *r = 0.5 * (a + b));
950 result
951}
952
953#[allow(dead_code)]
961pub fn minmod_ratio(r: f64) -> f64 {
962 if r <= 0.0 {
963 0.0
964 } else if r <= 1.0 {
965 r
966 } else {
967 1.0
968 }
969}
970
971#[allow(dead_code)]
973pub fn superbee_ratio(r: f64) -> f64 {
974 if r <= 0.0 {
975 0.0
976 } else {
977 (2.0 * r).min(1.0_f64).max(r.min(2.0_f64)).max(0.0_f64)
978 }
979}
980
981#[allow(dead_code)]
983pub fn van_leer_ratio(r: f64) -> f64 {
984 (r + r.abs()) / (1.0 + r.abs())
985}
986
987#[cfg(test)]
992mod flux_compute_tests {
993 use super::*;
994
995 #[test]
996 fn test_flux_grid3d_get_set() {
997 let mut g = FluxGrid3D::new(4, 4, 4, 0.1, 0.0);
998 g.set(1, 2, 3, 7.5);
999 assert!((g.get(1, 2, 3) - 7.5).abs() < 1e-12);
1000 }
1001
1002 #[test]
1003 fn test_flux_grid3d_clamped_boundary() {
1004 let g = FluxGrid3D::new(5, 5, 5, 0.1, 3.0);
1005 assert!((g.get_clamped(-1, 0, 0) - 3.0).abs() < 1e-12);
1007 assert!((g.get_clamped(100, 0, 0) - 3.0).abs() < 1e-12);
1008 }
1009
1010 #[test]
1011 fn test_l2_norm_uniform() {
1012 let g = FluxGrid3D::new(2, 2, 2, 0.5, 1.0);
1014 let expected = (8.0_f64).sqrt();
1015 assert!((g.l2_norm() - expected).abs() < 1e-10);
1016 }
1017
1018 #[test]
1019 fn test_minmod_same_sign() {
1020 assert!(
1021 (minmod(2.0, 3.0) - 2.0).abs() < 1e-12,
1022 "minmod picks smaller"
1023 );
1024 assert!((minmod(-1.0, -4.0) - (-1.0)).abs() < 1e-12);
1025 }
1026
1027 #[test]
1028 fn test_minmod_opposite_sign() {
1029 assert!(
1030 (minmod(2.0, -1.0)).abs() < 1e-12,
1031 "minmod returns 0 for opposite signs"
1032 );
1033 }
1034
1035 #[test]
1036 fn test_van_leer_smooth() {
1037 let vl = van_leer(2.0, 2.0);
1039 assert!(
1040 (vl - 2.0).abs() < 1e-12,
1041 "symmetric inputs: van_leer = value"
1042 );
1043 let vl2 = van_leer(1.0, 3.0);
1044 let expected = 2.0 * 1.0 * 3.0 / (1.0 + 3.0);
1045 assert!((vl2 - expected).abs() < 1e-12);
1046 }
1047
1048 #[test]
1049 fn test_upwind_flux_1d_positive_velocity() {
1050 let phi = vec![1.0; 10];
1052 let flux = upwind_flux_1d(&phi, 1.0, 0.1, 0.05);
1053 for f in &flux {
1054 assert!(
1055 f.abs() < 1e-12,
1056 "uniform field with positive vel: zero flux"
1057 );
1058 }
1059 }
1060
1061 #[test]
1062 fn test_lax_friedrichs_1d_preserves_mass() {
1063 let u: Vec<f64> = (0..20).map(|i| (i as f64 * 0.1 - 1.0).powi(2)).collect();
1066 let sum_before: f64 = u.iter().sum();
1067 let u_new = lax_friedrichs_advect_1d(&u, 0.5, 0.1, 0.01);
1068 let sum_after: f64 = u_new.iter().sum();
1069 assert!(
1071 (sum_after - sum_before).abs() < sum_before * 0.05 + 1.0,
1072 "mass should be approximately conserved"
1073 );
1074 }
1075
1076 #[test]
1077 fn test_divergence_constant_field_is_zero() {
1078 let n = 6;
1080 let mut vel = VectorFluxGrid3D::zeros(n, n, n, 0.1);
1081 for i in 0..n {
1082 for j in 0..n {
1083 for k in 0..n {
1084 vel.set_vel(i, j, k, [1.0, 2.0, 3.0]);
1085 }
1086 }
1087 }
1088 let div = divergence_3d(&vel);
1089 for &d in &div.values {
1090 assert!(
1091 d.abs() < 1e-10,
1092 "divergence of uniform field must be 0, got {d}"
1093 );
1094 }
1095 }
1096
1097 #[test]
1098 fn test_gradient_linear_field() {
1099 let n = 8;
1101 let dx = 0.25;
1102 let mut phi = FluxGrid3D::new(n, n, n, dx, 0.0);
1103 for i in 0..n {
1104 for j in 0..n {
1105 for k in 0..n {
1106 phi.set(i, j, k, i as f64 * dx);
1107 }
1108 }
1109 }
1110 let grad = gradient_3d(&phi);
1111 for i in 1..n - 1 {
1113 for j in 1..n - 1 {
1114 for k in 1..n - 1 {
1115 let gx = grad.u.get(i, j, k);
1116 assert!(
1117 (gx - 1.0).abs() < 1e-10,
1118 "gradient_x of linear field should be 1, got {gx} at ({i},{j},{k})"
1119 );
1120 }
1121 }
1122 }
1123 }
1124
1125 #[test]
1126 fn test_cfl_dt_uniform_velocity() {
1127 let mut vel = VectorFluxGrid3D::zeros(4, 4, 4, 0.1);
1128 for i in 0..4 {
1129 for j in 0..4 {
1130 for k in 0..4 {
1131 vel.set_vel(i, j, k, [2.0, 0.0, 0.0]);
1132 }
1133 }
1134 }
1135 let dt = cfl_dt(&vel, 0.5);
1137 assert!(
1138 (dt - 0.025).abs() < 1e-10,
1139 "cfl_dt should be 0.025, got {dt}"
1140 );
1141 }
1142
1143 #[test]
1144 fn test_cfl_dt_zero_velocity() {
1145 let vel = VectorFluxGrid3D::zeros(4, 4, 4, 0.1);
1146 let dt = cfl_dt(&vel, 0.5);
1147 assert!(dt.is_infinite(), "zero velocity → infinite dt");
1148 }
1149
1150 #[test]
1153 fn test_flux_grid3d_len_empty() {
1154 let g = FluxGrid3D::new(0, 5, 5, 0.1, 0.0);
1155 assert_eq!(g.len(), 0);
1156 assert!(g.is_empty());
1157 }
1158
1159 #[test]
1160 fn test_flux_grid3d_len_nonempty() {
1161 let g = FluxGrid3D::new(3, 4, 5, 0.1, 0.0);
1162 assert_eq!(g.len(), 60);
1163 assert!(!g.is_empty());
1164 }
1165
1166 #[test]
1167 fn test_flux_grid3d_max_val() {
1168 let mut g = FluxGrid3D::new(2, 2, 2, 0.1, 0.0);
1169 g.set(0, 0, 0, 5.0);
1170 g.set(1, 1, 1, -3.0);
1171 assert!((g.max_val() - 5.0).abs() < 1e-12);
1172 }
1173
1174 #[test]
1175 fn test_flux_grid3d_min_val() {
1176 let mut g = FluxGrid3D::new(2, 2, 2, 0.1, 0.0);
1177 g.set(0, 0, 0, 5.0);
1178 g.set(1, 1, 1, -3.0);
1179 assert!((g.min_val() - (-3.0)).abs() < 1e-12);
1180 }
1181
1182 #[test]
1183 fn test_flux_grid3d_l2_norm_zeros() {
1184 let g = FluxGrid3D::new(3, 3, 3, 0.1, 0.0);
1185 assert!(g.l2_norm() < 1e-15);
1186 }
1187
1188 #[test]
1189 fn test_flux_grid3d_index_layout() {
1190 let g = FluxGrid3D::new(3, 4, 5, 0.1, 0.0);
1191 assert_eq!(g.idx(0, 0, 0), 0);
1193 assert_eq!(g.idx(1, 0, 0), 20); assert_eq!(g.idx(0, 1, 0), 5); assert_eq!(g.idx(0, 0, 1), 1);
1196 }
1197
1198 #[test]
1199 fn test_flux_grid3d_clamped_low_indices() {
1200 let mut g = FluxGrid3D::new(5, 5, 5, 0.1, 0.0);
1201 g.set(0, 0, 0, 99.0);
1202 assert!((g.get_clamped(-1, 0, 0) - 99.0).abs() < 1e-12);
1204 assert!((g.get_clamped(0, -5, 0) - 99.0).abs() < 1e-12);
1205 }
1206
1207 #[test]
1210 fn test_vector_flux_grid_zeros() {
1211 let v = VectorFluxGrid3D::zeros(3, 3, 3, 0.1);
1212 for i in 0..3 {
1213 for j in 0..3 {
1214 for k in 0..3 {
1215 let vel = v.get_vel(i, j, k);
1216 assert_eq!(vel, [0.0, 0.0, 0.0]);
1217 }
1218 }
1219 }
1220 }
1221
1222 #[test]
1223 fn test_vector_flux_grid_set_get_vel() {
1224 let mut v = VectorFluxGrid3D::zeros(4, 4, 4, 0.1);
1225 v.set_vel(1, 2, 3, [1.5, 2.5, 3.5]);
1226 let vel = v.get_vel(1, 2, 3);
1227 assert!((vel[0] - 1.5).abs() < 1e-12);
1228 assert!((vel[1] - 2.5).abs() < 1e-12);
1229 assert!((vel[2] - 3.5).abs() < 1e-12);
1230 }
1231
1232 #[test]
1235 fn test_superbee_same_sign_positive() {
1236 let s = superbee(1.0, 2.0);
1238 assert!(s > 0.0, "superbee of positives should be positive, got {s}");
1239 }
1240
1241 #[test]
1242 fn test_superbee_opposite_signs() {
1243 assert_eq!(superbee(1.0, -1.0), 0.0);
1244 assert_eq!(superbee(-2.0, 3.0), 0.0);
1245 }
1246
1247 #[test]
1248 fn test_superbee_both_zero() {
1249 assert_eq!(superbee(0.0, 0.0), 0.0);
1250 }
1251
1252 #[test]
1253 fn test_mc_limiter_same_sign() {
1254 let mc = mc_limiter(2.0, 4.0);
1255 assert!(mc > 0.0 && mc <= 3.0, "mc_limiter = {mc}");
1257 }
1258
1259 #[test]
1260 fn test_mc_limiter_opposite_sign() {
1261 assert_eq!(mc_limiter(1.0, -1.0), 0.0);
1262 }
1263
1264 #[test]
1265 fn test_van_leer_zero_sum() {
1266 let vl = van_leer(2.0, -2.0);
1269 assert_eq!(vl, 0.0);
1270 }
1271
1272 #[test]
1273 fn test_minmod_equal_values() {
1274 assert!((minmod(3.0, 3.0) - 3.0).abs() < 1e-12);
1276 assert!((minmod(-2.0, -2.0) - (-2.0)).abs() < 1e-12);
1277 }
1278
1279 #[test]
1282 fn test_upwind_flux_1d_negative_velocity() {
1283 let phi = vec![1.0; 8];
1285 let flux = upwind_flux_1d(&phi, -1.0, 0.1, 0.05);
1286 for f in &flux {
1287 assert!(
1288 f.abs() < 1e-12,
1289 "uniform field with negative vel: zero flux"
1290 );
1291 }
1292 }
1293
1294 #[test]
1295 fn test_upwind_flux_1d_step_function() {
1296 let phi = vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0];
1298 let flux = upwind_flux_1d(&phi, 1.0, 0.1, 0.01);
1299 assert!(
1301 flux[3] < 0.0,
1302 "step up with positive vel: negative flux at boundary"
1303 );
1304 }
1305
1306 #[test]
1307 fn test_upwind_flux_1d_length() {
1308 let phi = vec![1.0; 7];
1309 let flux = upwind_flux_1d(&phi, 1.0, 0.1, 0.01);
1310 assert_eq!(flux.len(), 7);
1311 }
1312
1313 #[test]
1316 fn test_lax_friedrichs_flux_zero_difference() {
1317 let f = lax_friedrichs_flux(1.0, 1.0, 2.0, 2.0, 1.0);
1319 assert!((f - 2.0).abs() < 1e-12, "LF flux = {f}");
1320 }
1321
1322 #[test]
1323 fn test_lax_friedrichs_advect_1d_empty() {
1324 let result = lax_friedrichs_advect_1d(&[], 1.0, 0.1, 0.01);
1325 assert!(result.is_empty());
1326 }
1327
1328 #[test]
1329 fn test_lax_friedrichs_advect_1d_length() {
1330 let u = vec![1.0; 6];
1331 let u_new = lax_friedrichs_advect_1d(&u, 0.5, 0.1, 0.01);
1332 assert_eq!(u_new.len(), 6);
1333 }
1334
1335 #[test]
1336 fn test_lax_friedrichs_uniform_field_stable() {
1337 let u = vec![1.0; 10];
1339 let u_new = lax_friedrichs_advect_1d(&u, 1.0, 0.1, 0.05);
1340 let diff: f64 = u_new.iter().zip(u.iter()).map(|(a, b)| (a - b).abs()).sum();
1341 assert!(
1342 diff < 1e-10,
1343 "uniform field should not change under LF advection"
1344 );
1345 }
1346
1347 #[test]
1350 fn test_divergence_size_matches_input() {
1351 let vel = VectorFluxGrid3D::zeros(3, 4, 5, 0.1);
1352 let div = divergence_3d(&vel);
1353 assert_eq!(div.nx, 3);
1354 assert_eq!(div.ny, 4);
1355 assert_eq!(div.nz, 5);
1356 }
1357
1358 #[test]
1359 fn test_gradient_size_matches_input() {
1360 let phi = FluxGrid3D::new(3, 4, 5, 0.1, 0.0);
1361 let grad = gradient_3d(&phi);
1362 assert_eq!(grad.u.nx, 3);
1363 assert_eq!(grad.v.ny, 4);
1364 assert_eq!(grad.w.nz, 5);
1365 }
1366
1367 #[test]
1368 fn test_gradient_constant_field_is_zero() {
1369 let phi = FluxGrid3D::new(5, 5, 5, 0.1, 7.0);
1370 let grad = gradient_3d(&phi);
1371 for &g in &grad.u.values {
1372 assert!(g.abs() < 1e-10, "gradient of constant = 0");
1373 }
1374 }
1375
1376 #[test]
1377 fn test_divergence_linear_vx_field() {
1378 let n = 8;
1380 let dx = 0.5;
1381 let mut vel = VectorFluxGrid3D::zeros(n, n, n, dx);
1382 for i in 0..n {
1383 for j in 0..n {
1384 for k in 0..n {
1385 vel.u.set(i, j, k, i as f64 * dx);
1386 }
1387 }
1388 }
1389 let div = divergence_3d(&vel);
1390 for i in 1..n - 1 {
1392 for j in 1..n - 1 {
1393 for k in 1..n - 1 {
1394 let d = div.get(i, j, k);
1395 assert!(
1396 (d - 1.0).abs() < 1e-10,
1397 "div at ({i},{j},{k}) = {d}, expected 1"
1398 );
1399 }
1400 }
1401 }
1402 }
1403
1404 #[test]
1407 fn test_euler_step_advect_preserves_size() {
1408 let phi = FluxGrid3D::new(4, 4, 4, 0.1, 1.0);
1409 let mut vel = VectorFluxGrid3D::zeros(4, 4, 4, 0.1);
1410 for i in 0..4 {
1411 for j in 0..4 {
1412 for k in 0..4 {
1413 vel.set_vel(i, j, k, [1.0, 0.0, 0.0]);
1414 }
1415 }
1416 }
1417 let phi_new = euler_step_advect(&phi, &vel, 0.01);
1418 assert_eq!(phi_new.nx, phi.nx);
1419 assert_eq!(phi_new.ny, phi.ny);
1420 assert_eq!(phi_new.nz, phi.nz);
1421 }
1422
1423 #[test]
1424 fn test_euler_step_constant_phi_no_change() {
1425 let phi = FluxGrid3D::new(5, 5, 5, 0.1, 3.0);
1427 let mut vel = VectorFluxGrid3D::zeros(5, 5, 5, 0.1);
1428 for i in 0..5 {
1429 for j in 0..5 {
1430 for k in 0..5 {
1431 vel.set_vel(i, j, k, [1.0, 1.0, 1.0]);
1432 }
1433 }
1434 }
1435 let phi_new = euler_step_advect(&phi, &vel, 0.01);
1436 for (&a, &b) in phi.values.iter().zip(phi_new.values.iter()) {
1437 assert!(
1438 (a - b).abs() < 1e-10,
1439 "constant phi should not change: {a} vs {b}"
1440 );
1441 }
1442 }
1443
1444 #[test]
1447 fn test_cfl_dt_negative_velocity() {
1448 let mut vel = VectorFluxGrid3D::zeros(4, 4, 4, 0.2);
1450 vel.set_vel(0, 0, 0, [0.0, -4.0, 0.0]);
1451 let dt = cfl_dt(&vel, 1.0);
1452 assert!((dt - 0.05).abs() < 1e-10, "cfl_dt = {dt}, expected 0.05");
1453 }
1454
1455 #[test]
1456 fn test_cfl_dt_multiple_components() {
1457 let mut vel = VectorFluxGrid3D::zeros(3, 3, 3, 0.1);
1458 vel.set_vel(1, 1, 1, [1.0, 2.0, 3.0]);
1460 let dt = cfl_dt(&vel, 1.0);
1461 assert!((dt - 0.1 / 3.0).abs() < 1e-10, "cfl_dt = {dt}");
1462 }
1463
1464 #[test]
1467 fn test_euler_state_from_primitives() {
1468 let gamma = 1.4;
1469 let s = EulerState::from_primitives(1.0, 0.1, 0.0, 0.0, 1.0, gamma);
1470 assert!((s.rho - 1.0).abs() < 1e-12);
1471 let p_back = s.pressure(gamma);
1472 assert!((p_back - 1.0).abs() < 1e-10, "pressure roundtrip: {p_back}");
1473 }
1474
1475 #[test]
1476 fn test_euler_state_velocity() {
1477 let gamma = 1.4;
1478 let s = EulerState::from_primitives(2.0, 3.0, 4.0, 5.0, 1.0, gamma);
1479 let u = s.velocity();
1480 assert!((u[0] - 3.0).abs() < 1e-12);
1481 assert!((u[1] - 4.0).abs() < 1e-12);
1482 assert!((u[2] - 5.0).abs() < 1e-12);
1483 }
1484
1485 #[test]
1486 fn test_euler_state_sound_speed() {
1487 let gamma = 1.4;
1488 let s = EulerState::from_primitives(1.0, 0.0, 0.0, 0.0, 1.0, gamma);
1490 let c = s.sound_speed(gamma);
1491 let expected = (gamma).sqrt();
1492 assert!(
1493 (c - expected).abs() < 1e-10,
1494 "sound speed = {c}, expected {expected}"
1495 );
1496 }
1497
1498 #[test]
1499 fn test_euler_state_lerp() {
1500 let gamma = 1.4;
1501 let s0 = EulerState::from_primitives(1.0, 0.0, 0.0, 0.0, 1.0, gamma);
1502 let s1 = EulerState::from_primitives(2.0, 0.0, 0.0, 0.0, 2.0, gamma);
1503 let s05 = s0.lerp(&s1, 0.5);
1504 assert!((s05.rho - 1.5).abs() < 1e-12, "lerp rho = {}", s05.rho);
1505 }
1506
1507 #[test]
1508 fn test_euler_state_flux_x() {
1509 let gamma = 1.4;
1510 let s = EulerState::from_primitives(1.0, 0.0, 0.0, 0.0, 1.0, gamma);
1512 let f = s.flux_x(gamma);
1513 assert!(f[0].abs() < 1e-12, "mass flux at rest = {}", f[0]);
1514 assert!((f[1] - 1.0).abs() < 1e-10, "pressure flux = {}", f[1]);
1515 }
1516
1517 #[test]
1520 fn test_van_albada_same_sign() {
1521 let v = van_albada(2.0, 4.0);
1523 assert!(v > 0.0, "van_albada(2,4) = {v}");
1524 assert!(v <= 4.0);
1526 }
1527
1528 #[test]
1529 fn test_van_albada_opposite_signs() {
1530 assert_eq!(van_albada(1.0, -1.0), 0.0);
1531 assert_eq!(van_albada(-2.0, 3.0), 0.0);
1532 }
1533
1534 #[test]
1535 fn test_van_albada_symmetry() {
1536 let v1 = van_albada(1.0, 3.0);
1538 let v2 = van_albada(3.0, 1.0);
1539 assert!(v1 > 0.0 && v2 > 0.0);
1540 }
1541
1542 #[test]
1545 fn test_muscl_reconstruct_uniform() {
1546 let u = vec![1.0_f64; 10];
1548 let (u_l, u_r) = muscl_reconstruct(&u, 3, minmod);
1549 assert!((u_l - 1.0).abs() < 1e-12, "uniform MUSCL u_l = {u_l}");
1550 assert!((u_r - 1.0).abs() < 1e-12, "uniform MUSCL u_r = {u_r}");
1551 }
1552
1553 #[test]
1554 fn test_muscl_reconstruct_all_length() {
1555 let u = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1556 let pairs = muscl_reconstruct_all(&u, minmod);
1557 assert_eq!(pairs.len(), 4, "MUSCL all: expected 4 interfaces");
1558 }
1559
1560 #[test]
1561 fn test_muscl_reconstruct_all_monotone() {
1562 let u: Vec<f64> = (0..8).map(|i| i as f64).collect();
1564 let pairs = muscl_reconstruct_all(&u, minmod);
1565 for (i, &(u_l, u_r)) in pairs.iter().enumerate() {
1566 assert!(u_l <= u_r + 1e-10, "interface {i}: u_l={u_l} > u_r={u_r}");
1567 }
1568 }
1569
1570 #[test]
1573 fn test_godunov_flux_advection_positive_speed() {
1574 let f = godunov_flux_advection(2.0, 3.0, 1.0);
1576 assert!((f - 2.0).abs() < 1e-12, "Godunov advection +speed: {f}");
1577 }
1578
1579 #[test]
1580 fn test_godunov_flux_advection_negative_speed() {
1581 let f = godunov_flux_advection(2.0, 3.0, -1.0);
1582 assert!((f - (-3.0)).abs() < 1e-12, "Godunov advection -speed: {f}");
1583 }
1584
1585 #[test]
1586 fn test_godunov_flux_burgers_shock() {
1587 let f = godunov_flux_burgers(2.0, 1.0);
1589 assert!((f - 0.5 * 4.0).abs() < 1e-12, "Burgers shock: {f}");
1590 }
1591
1592 #[test]
1593 fn test_godunov_flux_burgers_rarefaction_sonic() {
1594 let f = godunov_flux_burgers(-1.0, 1.0);
1596 assert!(f.abs() < 1e-12, "Burgers sonic: {f}");
1597 }
1598
1599 #[test]
1600 fn test_godunov_flux_burgers_rarefaction_positive() {
1601 let f = godunov_flux_burgers(1.0, 2.0);
1603 assert!((f - 0.5).abs() < 1e-12, "Burgers pos rarefaction: {f}");
1604 }
1605
1606 #[test]
1609 fn test_roe_flux_scalar_symmetry() {
1610 let f = roe_flux_scalar(2.0, 2.0, 1.5);
1612 assert!((f - 1.5 * 2.0).abs() < 1e-12, "Roe scalar uniform: {f}");
1613 }
1614
1615 #[test]
1616 fn test_roe_flux_scalar_upwind() {
1617 let f = roe_flux_scalar(1.0, 2.0, 1.0);
1619 assert!((f - 1.0).abs() < 1e-12, "Roe scalar upwind: {f}");
1621 }
1622
1623 #[test]
1624 fn test_roe_flux_euler_1d_finite() {
1625 let f = roe_flux_euler_1d(1.0, 0.1, 1.0, 1.0, 0.1, 1.0, 1.4);
1626 for (i, &fi) in f.iter().enumerate() {
1627 assert!(fi.is_finite(), "Roe Euler flux[{i}] not finite: {fi}");
1628 }
1629 }
1630
1631 #[test]
1634 fn test_hll_flux_subsonic() {
1635 let f = hll_flux(1.0, 2.0, 1.0, 2.0, -1.0, 1.0);
1637 assert!(f.is_finite(), "HLL subsonic: {f}");
1638 }
1639
1640 #[test]
1641 fn test_hll_flux_supersonic_right() {
1642 let f = hll_flux(1.0, 2.0, 3.0, 4.0, 1.0, 2.0);
1644 assert!((f - 3.0).abs() < 1e-12, "HLL supersonic right: {f}");
1645 }
1646
1647 #[test]
1648 fn test_hll_flux_supersonic_left() {
1649 let f = hll_flux(1.0, 2.0, 3.0, 4.0, -2.0, -1.0);
1651 assert!((f - 4.0).abs() < 1e-12, "HLL supersonic left: {f}");
1652 }
1653
1654 #[test]
1655 fn test_hll_flux_euler_1d_finite() {
1656 let f = hll_flux_euler_1d(1.0, 0.1, 1.0, 1.2, 0.05, 1.1, 1.4);
1657 for &fi in &f {
1658 assert!(fi.is_finite(), "HLL Euler flux not finite");
1659 }
1660 }
1661
1662 #[test]
1663 fn test_hll_wave_speeds() {
1664 let (s_l, s_r) = hll_wave_speeds(0.5, 1.0, -0.5, 1.0);
1665 assert!(s_l < 0.0 && s_r > 0.0, "wave speeds s_l={s_l} s_r={s_r}");
1666 assert!(s_l <= s_r);
1667 }
1668
1669 #[test]
1672 fn test_hllc_flux_euler_1d_finite() {
1673 let f = hllc_flux_euler_1d(1.0, 0.1, 1.0, 1.2, 0.05, 1.1, 1.4);
1674 for &fi in &f {
1675 assert!(fi.is_finite(), "HLLC Euler flux not finite: {fi}");
1676 }
1677 }
1678
1679 #[test]
1680 fn test_hllc_agrees_with_hll_at_uniform_state() {
1681 let f_hll = hll_flux_euler_1d(1.0, 0.1, 1.0, 1.0, 0.1, 1.0, 1.4);
1683 let f_hllc = hllc_flux_euler_1d(1.0, 0.1, 1.0, 1.0, 0.1, 1.0, 1.4);
1684 for i in 0..3 {
1685 assert!(
1686 (f_hll[i] - f_hllc[i]).abs() < 1e-6,
1687 "HLL vs HLLC at [{}]: {} vs {}",
1688 i,
1689 f_hll[i],
1690 f_hllc[i]
1691 );
1692 }
1693 }
1694
1695 #[test]
1698 fn test_characteristic_roundtrip() {
1699 let u0 = 1.5;
1700 let u1 = 0.7;
1701 let a = 2.0;
1702 let (wp, wm) = characteristic_decompose_2wave(u0, u1, a);
1703 let (u0_back, u1_back) = characteristic_recompose_2wave(wp, wm, a);
1704 assert!((u0_back - u0).abs() < 1e-12, "roundtrip u0: {u0_back}");
1705 assert!((u1_back - u1).abs() < 1e-12, "roundtrip u1: {u1_back}");
1706 }
1707
1708 #[test]
1709 fn test_characteristic_limited_reconstruct_finite() {
1710 let u0: Vec<f64> = (0..8).map(|i| i as f64 * 0.1).collect();
1711 let u1: Vec<f64> = vec![0.5; 8];
1712 let (left, right) = characteristic_limited_reconstruct(&u0, &u1, 3, 1.0, minmod);
1713 for &v in left.iter().chain(right.iter()) {
1714 assert!(v.is_finite(), "characteristic reconstruct not finite: {v}");
1715 }
1716 }
1717
1718 #[test]
1721 fn test_tvd_rk2_advect_uniform_unchanged() {
1722 let phi = FluxGrid3D::new(5, 5, 5, 0.1, 2.0);
1723 let mut vel = VectorFluxGrid3D::zeros(5, 5, 5, 0.1);
1724 for i in 0..5 {
1725 for j in 0..5 {
1726 for k in 0..5 {
1727 vel.set_vel(i, j, k, [1.0, 0.0, 0.0]);
1728 }
1729 }
1730 }
1731 let phi_new = tvd_rk2_advect(&phi, &vel, 0.01);
1732 for (&a, &b) in phi.values.iter().zip(phi_new.values.iter()) {
1733 assert!((a - b).abs() < 1e-10, "TVD RK2 uniform: {a} vs {b}");
1734 }
1735 }
1736
1737 #[test]
1738 fn test_tvd_rk2_advect_size_preserved() {
1739 let phi = FluxGrid3D::new(4, 3, 2, 0.1, 1.0);
1740 let vel = VectorFluxGrid3D::zeros(4, 3, 2, 0.1);
1741 let phi_new = tvd_rk2_advect(&phi, &vel, 0.01);
1742 assert_eq!(phi_new.nx, phi.nx);
1743 assert_eq!(phi_new.ny, phi.ny);
1744 assert_eq!(phi_new.nz, phi.nz);
1745 }
1746
1747 #[test]
1750 fn test_minmod_ratio_clamp() {
1751 assert_eq!(minmod_ratio(-0.5), 0.0);
1752 assert!((minmod_ratio(0.5) - 0.5).abs() < 1e-12);
1753 assert!((minmod_ratio(2.0) - 1.0).abs() < 1e-12);
1754 }
1755
1756 #[test]
1757 fn test_superbee_ratio_basic() {
1758 assert_eq!(superbee_ratio(-1.0), 0.0);
1759 let v = superbee_ratio(1.5);
1760 assert!(v > 0.0 && v <= 2.0, "superbee_ratio(1.5) = {v}");
1761 }
1762
1763 #[test]
1764 fn test_van_leer_ratio_basic() {
1765 let v = van_leer_ratio(1.0);
1767 assert!((v - 1.0).abs() < 1e-12, "van_leer_ratio(1) = {v}");
1768 assert_eq!(van_leer_ratio(-0.5), 0.0);
1770 }
1771
1772 #[test]
1773 fn test_van_leer_ratio_monotone() {
1774 let v1 = van_leer_ratio(0.5);
1776 let v2 = van_leer_ratio(1.0);
1777 let v3 = van_leer_ratio(2.0);
1778 assert!(v1 <= v2, "van_leer not monotone: {v1} > {v2}");
1779 assert!(v2 <= v3 + 1e-12, "van_leer not monotone: {v2} > {v3}");
1780 }
1781}