1use rayon::prelude::*;
13
14#[derive(Debug, Clone)]
22pub struct FluxGrid3D {
23 pub nx: usize,
25 pub ny: usize,
27 pub nz: usize,
29 pub dx: f64,
31 pub values: Vec<f64>,
33}
34
35impl FluxGrid3D {
36 pub fn new(nx: usize, ny: usize, nz: usize, dx: f64, fill: f64) -> Self {
38 Self {
39 nx,
40 ny,
41 nz,
42 dx,
43 values: vec![fill; nx * ny * nz],
44 }
45 }
46
47 #[inline]
49 pub fn idx(&self, i: usize, j: usize, k: usize) -> usize {
50 i * self.ny * self.nz + j * self.nz + k
51 }
52
53 #[inline]
55 pub fn get(&self, i: usize, j: usize, k: usize) -> f64 {
56 self.values[self.idx(i, j, k)]
57 }
58
59 #[inline]
61 pub fn set(&mut self, i: usize, j: usize, k: usize, v: f64) {
62 let idx = self.idx(i, j, k);
63 self.values[idx] = v;
64 }
65
66 pub fn len(&self) -> usize {
68 self.nx * self.ny * self.nz
69 }
70
71 pub fn is_empty(&self) -> bool {
73 self.len() == 0
74 }
75
76 pub fn get_clamped(&self, i: isize, j: isize, k: isize) -> f64 {
79 let ci = i.clamp(0, self.nx as isize - 1) as usize;
80 let cj = j.clamp(0, self.ny as isize - 1) as usize;
81 let ck = k.clamp(0, self.nz as isize - 1) as usize;
82 self.get(ci, cj, ck)
83 }
84
85 pub fn l2_norm(&self) -> f64 {
87 let sum_sq: f64 = self.values.iter().map(|&v| v * v).sum();
88 sum_sq.sqrt()
89 }
90
91 pub fn max_val(&self) -> f64 {
93 self.values
94 .iter()
95 .copied()
96 .fold(f64::NEG_INFINITY, f64::max)
97 }
98
99 pub fn min_val(&self) -> f64 {
101 self.values.iter().copied().fold(f64::INFINITY, f64::min)
102 }
103}
104
105#[derive(Debug, Clone)]
114pub struct VectorFluxGrid3D {
115 pub u: FluxGrid3D,
117 pub v: FluxGrid3D,
119 pub w: FluxGrid3D,
121}
122
123impl VectorFluxGrid3D {
124 pub fn zeros(nx: usize, ny: usize, nz: usize, dx: f64) -> Self {
126 Self {
127 u: FluxGrid3D::new(nx, ny, nz, dx, 0.0),
128 v: FluxGrid3D::new(nx, ny, nz, dx, 0.0),
129 w: FluxGrid3D::new(nx, ny, nz, dx, 0.0),
130 }
131 }
132
133 pub fn set_vel(&mut self, i: usize, j: usize, k: usize, vel: [f64; 3]) {
135 self.u.set(i, j, k, vel[0]);
136 self.v.set(i, j, k, vel[1]);
137 self.w.set(i, j, k, vel[2]);
138 }
139
140 pub fn get_vel(&self, i: usize, j: usize, k: usize) -> [f64; 3] {
142 [
143 self.u.get(i, j, k),
144 self.v.get(i, j, k),
145 self.w.get(i, j, k),
146 ]
147 }
148}
149
150#[inline]
158pub fn minmod(a: f64, b: f64) -> f64 {
159 if a * b <= 0.0 {
160 0.0
161 } else if a.abs() < b.abs() {
162 a
163 } else {
164 b
165 }
166}
167
168pub fn superbee(a: f64, b: f64) -> f64 {
170 if a * b <= 0.0 {
171 return 0.0;
172 }
173 let s = if a >= 0.0 { 1.0 } else { -1.0 };
174 s * a.abs().max(b.abs()).min(2.0 * a.abs().min(b.abs()))
175}
176
177pub fn van_leer(a: f64, b: f64) -> f64 {
179 if a * b <= 0.0 {
180 return 0.0;
181 }
182 2.0 * a * b / (a + b)
183}
184
185pub fn mc_limiter(a: f64, b: f64) -> f64 {
187 if a * b <= 0.0 {
188 return 0.0;
189 }
190 let centered = 0.5 * (a + b);
191 let s = if a >= 0.0 { 1.0 } else { -1.0 };
192 s * centered.abs().min(2.0 * a.abs()).min(2.0 * b.abs())
193}
194
195pub fn upwind_flux_1d(phi: &[f64], vel: f64, dx: f64, dt: f64) -> Vec<f64> {
206 let n = phi.len();
207 let mut flux = vec![0.0; n];
208 let cfl = vel * dt / dx;
209 for i in 0..n {
210 let phi_l = if i == 0 { phi[0] } else { phi[i - 1] };
211 let phi_r = if i + 1 >= n { phi[n - 1] } else { phi[i + 1] };
212 if vel >= 0.0 {
213 flux[i] = -cfl * (phi[i] - phi_l);
215 } else {
216 flux[i] = -cfl * (phi_r - phi[i]);
218 }
219 }
220 flux
221}
222
223pub fn advect_upwind_3d(phi: &FluxGrid3D, vel: &VectorFluxGrid3D, dt: f64) -> FluxGrid3D {
227 let _nx = phi.nx;
228 let ny = phi.ny;
229 let nz = phi.nz;
230 let dx = phi.dx;
231
232 let mut out = phi.clone();
233
234 out.values.par_iter_mut().enumerate().for_each(|(idx, v)| {
235 let i = idx / (ny * nz);
236 let j = (idx / nz) % ny;
237 let k = idx % nz;
238
239 let ux = vel.u.get(i, j, k);
240 let uy = vel.v.get(i, j, k);
241 let uz = vel.w.get(i, j, k);
242
243 let phi_xm = phi.get_clamped(i as isize - 1, j as isize, k as isize);
244 let phi_xp = phi.get_clamped(i as isize + 1, j as isize, k as isize);
245 let phi_ym = phi.get_clamped(i as isize, j as isize - 1, k as isize);
246 let phi_yp = phi.get_clamped(i as isize, j as isize + 1, k as isize);
247 let phi_zm = phi.get_clamped(i as isize, j as isize, k as isize - 1);
248 let phi_zp = phi.get_clamped(i as isize, j as isize, k as isize + 1);
249 let phi_c = phi.get(i, j, k);
250
251 let flux_x = if ux >= 0.0 {
252 ux * (phi_c - phi_xm) / dx
253 } else {
254 ux * (phi_xp - phi_c) / dx
255 };
256 let flux_y = if uy >= 0.0 {
257 uy * (phi_c - phi_ym) / dx
258 } else {
259 uy * (phi_yp - phi_c) / dx
260 };
261 let flux_z = if uz >= 0.0 {
262 uz * (phi_c - phi_zm) / dx
263 } else {
264 uz * (phi_zp - phi_c) / dx
265 };
266
267 *v = phi_c - dt * (flux_x + flux_y + flux_z);
268 });
269
270 out
271}
272
273#[inline]
281pub fn lax_friedrichs_flux(u_l: f64, u_r: f64, f_l: f64, f_r: f64, alpha: f64) -> f64 {
282 0.5 * (f_l + f_r) - 0.5 * alpha * (u_r - u_l)
283}
284
285pub fn lax_friedrichs_advect_1d(u: &[f64], vel: f64, dx: f64, dt: f64) -> Vec<f64> {
290 let n = u.len();
291 if n == 0 {
292 return Vec::new();
293 }
294 let alpha = vel.abs();
295 let mut u_new = vec![0.0; n];
296 for i in 0..n {
297 let u_l = if i == 0 { u[0] } else { u[i - 1] };
298 let u_r = if i + 1 >= n { u[n - 1] } else { u[i + 1] };
299 let f_l = vel * u_l;
300 let f_r = vel * u_r;
301 let f_left = lax_friedrichs_flux(u_l, u[i], f_l, vel * u[i], alpha);
302 let f_right = lax_friedrichs_flux(u[i], u_r, vel * u[i], f_r, alpha);
303 u_new[i] = u[i] - dt / dx * (f_right - f_left);
304 }
305 u_new
306}
307
308pub fn divergence_3d(vel: &VectorFluxGrid3D) -> FluxGrid3D {
316 let nx = vel.u.nx;
317 let ny = vel.u.ny;
318 let nz = vel.u.nz;
319 let dx = vel.u.dx;
320
321 let mut div = FluxGrid3D::new(nx, ny, nz, dx, 0.0);
322 div.values.par_iter_mut().enumerate().for_each(|(idx, d)| {
323 let i = idx / (ny * nz);
324 let j = (idx / nz) % ny;
325 let k = idx % nz;
326 let ii = i as isize;
327 let jj = j as isize;
328 let kk = k as isize;
329 let du_dx =
330 (vel.u.get_clamped(ii + 1, jj, kk) - vel.u.get_clamped(ii - 1, jj, kk)) / (2.0 * dx);
331 let dv_dy =
332 (vel.v.get_clamped(ii, jj + 1, kk) - vel.v.get_clamped(ii, jj - 1, kk)) / (2.0 * dx);
333 let dw_dz =
334 (vel.w.get_clamped(ii, jj, kk + 1) - vel.w.get_clamped(ii, jj, kk - 1)) / (2.0 * dx);
335 *d = du_dx + dv_dy + dw_dz;
336 });
337 div
338}
339
340pub fn gradient_3d(phi: &FluxGrid3D) -> VectorFluxGrid3D {
344 let nx = phi.nx;
345 let ny = phi.ny;
346 let nz = phi.nz;
347 let dx = phi.dx;
348
349 let mut grad = VectorFluxGrid3D::zeros(nx, ny, nz, dx);
350
351 let u_vals: Vec<f64> = (0..nx * ny * nz)
352 .into_par_iter()
353 .map(|idx| {
354 let i = idx / (ny * nz);
355 let j = (idx / nz) % ny;
356 let k = idx % nz;
357 let ii = i as isize;
358 let jj = j as isize;
359 let kk = k as isize;
360 (phi.get_clamped(ii + 1, jj, kk) - phi.get_clamped(ii - 1, jj, kk)) / (2.0 * dx)
361 })
362 .collect();
363
364 let v_vals: Vec<f64> = (0..nx * ny * nz)
365 .into_par_iter()
366 .map(|idx| {
367 let i = idx / (ny * nz);
368 let j = (idx / nz) % ny;
369 let k = idx % nz;
370 let ii = i as isize;
371 let jj = j as isize;
372 let kk = k as isize;
373 (phi.get_clamped(ii, jj + 1, kk) - phi.get_clamped(ii, jj - 1, kk)) / (2.0 * dx)
374 })
375 .collect();
376
377 let w_vals: Vec<f64> = (0..nx * ny * nz)
378 .into_par_iter()
379 .map(|idx| {
380 let i = idx / (ny * nz);
381 let j = (idx / nz) % ny;
382 let k = idx % nz;
383 let ii = i as isize;
384 let jj = j as isize;
385 let kk = k as isize;
386 (phi.get_clamped(ii, jj, kk + 1) - phi.get_clamped(ii, jj, kk - 1)) / (2.0 * dx)
387 })
388 .collect();
389
390 grad.u.values = u_vals;
391 grad.v.values = v_vals;
392 grad.w.values = w_vals;
393 grad
394}
395
396pub fn euler_step_advect(phi: &FluxGrid3D, vel: &VectorFluxGrid3D, dt: f64) -> FluxGrid3D {
404 advect_upwind_3d(phi, vel, dt)
405}
406
407pub fn cfl_dt(vel: &VectorFluxGrid3D, cfl_factor: f64) -> f64 {
411 let max_u = vel
412 .u
413 .values
414 .iter()
415 .copied()
416 .map(f64::abs)
417 .fold(0.0_f64, f64::max);
418 let max_v = vel
419 .v
420 .values
421 .iter()
422 .copied()
423 .map(f64::abs)
424 .fold(0.0_f64, f64::max);
425 let max_w = vel
426 .w
427 .values
428 .iter()
429 .copied()
430 .map(f64::abs)
431 .fold(0.0_f64, f64::max);
432 let max_vel = max_u.max(max_v).max(max_w);
433 let dx = vel.u.dx;
434 if max_vel < 1e-300 {
435 f64::INFINITY
436 } else {
437 cfl_factor * dx / max_vel
438 }
439}
440
441#[derive(Debug, Clone, Copy)]
447pub struct EulerState {
448 pub rho: f64,
450 pub rho_u: f64,
452 pub rho_v: f64,
454 pub rho_w: f64,
456 pub e: f64,
458}
459
460impl EulerState {
461 pub fn from_primitives(rho: f64, u: f64, v: f64, w: f64, p: f64, gamma: f64) -> Self {
463 let ke = 0.5 * rho * (u * u + v * v + w * w);
464 let e = p / (gamma - 1.0) + ke;
465 Self {
466 rho,
467 rho_u: rho * u,
468 rho_v: rho * v,
469 rho_w: rho * w,
470 e,
471 }
472 }
473
474 pub fn velocity(&self) -> [f64; 3] {
476 let rho = if self.rho.abs() > 1e-30 {
477 self.rho
478 } else {
479 1e-30
480 };
481 [self.rho_u / rho, self.rho_v / rho, self.rho_w / rho]
482 }
483
484 pub fn pressure(&self, gamma: f64) -> f64 {
486 let u = self.velocity();
487 let ke = 0.5 * self.rho * (u[0] * u[0] + u[1] * u[1] + u[2] * u[2]);
488 (gamma - 1.0) * (self.e - ke)
489 }
490
491 pub fn sound_speed(&self, gamma: f64) -> f64 {
493 let p = self.pressure(gamma);
494 let rho = if self.rho.abs() > 1e-30 {
495 self.rho
496 } else {
497 1e-30
498 };
499 (gamma * p / rho).sqrt().max(0.0)
500 }
501
502 pub fn flux_x(&self, gamma: f64) -> [f64; 5] {
504 let u = self.velocity();
505 let p = self.pressure(gamma);
506 [
507 self.rho_u,
508 self.rho_u * u[0] + p,
509 self.rho_v * u[0],
510 self.rho_w * u[0],
511 (self.e + p) * u[0],
512 ]
513 }
514
515 pub fn lerp(&self, other: &EulerState, t: f64) -> EulerState {
517 EulerState {
518 rho: self.rho + t * (other.rho - self.rho),
519 rho_u: self.rho_u + t * (other.rho_u - self.rho_u),
520 rho_v: self.rho_v + t * (other.rho_v - self.rho_v),
521 rho_w: self.rho_w + t * (other.rho_w - self.rho_w),
522 e: self.e + t * (other.e - self.e),
523 }
524 }
525}
526
527pub fn van_albada(a: f64, b: f64) -> f64 {
535 if a * b <= 0.0 {
536 return 0.0;
537 }
538 let r = a / b;
539 b * (r * r + r) / (r * r + 1.0)
540}
541
542pub fn muscl_reconstruct(u: &[f64], i: usize, limiter: fn(f64, f64) -> f64) -> (f64, f64) {
555 let n = u.len();
556 let u_im = if i == 0 { u[0] } else { u[i - 1] };
557 let u_i = u[i];
558 let u_ip = if i + 1 < n { u[i + 1] } else { u[n - 1] };
559 let u_ipp = if i + 2 < n { u[i + 2] } else { u[n - 1] };
560
561 let delta_m = u_i - u_im;
562 let delta_p = u_ip - u_i;
563 let delta_pp = u_ipp - u_ip;
564
565 let sigma_l = limiter(delta_m, delta_p);
566 let sigma_r = limiter(delta_p, delta_pp);
567
568 let u_l = u_i + 0.5 * sigma_l;
569 let u_r = u_ip - 0.5 * sigma_r;
570 (u_l, u_r)
571}
572
573pub fn muscl_reconstruct_all(u: &[f64], limiter: fn(f64, f64) -> f64) -> Vec<(f64, f64)> {
577 let n = u.len();
578 if n < 2 {
579 return Vec::new();
580 }
581 (0..n - 1)
582 .map(|i| muscl_reconstruct(u, i, limiter))
583 .collect()
584}
585
586pub fn godunov_flux_advection(u_l: f64, u_r: f64, wave_speed: f64) -> f64 {
594 if wave_speed >= 0.0 {
595 wave_speed * u_l
596 } else {
597 wave_speed * u_r
598 }
599}
600
601pub fn godunov_flux_burgers(u_l: f64, u_r: f64) -> f64 {
605 if u_l >= u_r {
608 let s = 0.5 * (u_l + u_r);
610 if s >= 0.0 {
611 0.5 * u_l * u_l
612 } else {
613 0.5 * u_r * u_r
614 }
615 } else {
616 if u_l >= 0.0 {
618 0.5 * u_l * u_l
619 } else if u_r <= 0.0 {
620 0.5 * u_r * u_r
621 } else {
622 0.0 }
624 }
625}
626
627pub fn roe_flux_scalar(u_l: f64, u_r: f64, a_roe: f64) -> f64 {
636 let f_l = a_roe * u_l;
637 let f_r = a_roe * u_r;
638 0.5 * (f_l + f_r) - 0.5 * a_roe.abs() * (u_r - u_l)
639}
640
641pub fn roe_flux_euler_1d(
646 rho_l: f64,
647 u_l: f64,
648 p_l: f64,
649 rho_r: f64,
650 u_r: f64,
651 p_r: f64,
652 gamma: f64,
653) -> [f64; 3] {
654 let sqrt_rl = rho_l.sqrt();
656 let sqrt_rr = rho_r.sqrt();
657 let denom = sqrt_rl + sqrt_rr;
658
659 let u_roe = (sqrt_rl * u_l + sqrt_rr * u_r) / denom;
660 let h_l = (p_l / (rho_l * (gamma - 1.0)) + p_l / rho_l) + 0.5 * u_l * u_l;
661 let h_r = (p_r / (rho_r * (gamma - 1.0)) + p_r / rho_r) + 0.5 * u_r * u_r;
662 let h_roe = (sqrt_rl * h_l + sqrt_rr * h_r) / denom;
663
664 let a2 = (gamma - 1.0) * (h_roe - 0.5 * u_roe * u_roe);
665 let a_roe = if a2 > 0.0 { a2.sqrt() } else { 0.0 };
666
667 let e_l = p_l / (gamma - 1.0) + 0.5 * rho_l * u_l * u_l;
669 let e_r = p_r / (gamma - 1.0) + 0.5 * rho_r * u_r * u_r;
670
671 let fl = [rho_l * u_l, rho_l * u_l * u_l + p_l, (e_l + p_l) * u_l];
673 let fr = [rho_r * u_r, rho_r * u_r * u_r + p_r, (e_r + p_r) * u_r];
674
675 let du = [rho_r - rho_l, rho_r * u_r - rho_l * u_l, e_r - e_l];
677
678 let lam = [u_roe - a_roe, u_roe, u_roe + a_roe];
680
681 let r_inv_du = [
683 (du[0] * u_roe - du[1]) / (2.0 * a_roe.max(1e-30)),
684 du[0] - (du[0] * u_roe - du[1]) / (a_roe.max(1e-30)),
685 (du[0] * u_roe + du[1]) / (2.0 * a_roe.max(1e-30)),
686 ];
687
688 let r_mat = [
690 [1.0, 1.0, 1.0],
691 [u_roe - a_roe, u_roe, u_roe + a_roe],
692 [
693 h_roe - u_roe * a_roe,
694 0.5 * u_roe * u_roe,
695 h_roe + u_roe * a_roe,
696 ],
697 ];
698
699 let mut dissipation = [0.0f64; 3];
700 for i in 0..3 {
701 for k in 0..3 {
702 dissipation[i] += lam[k].abs() * r_inv_du[k] * r_mat[i][k];
703 }
704 }
705
706 [
707 0.5 * (fl[0] + fr[0]) - 0.5 * dissipation[0],
708 0.5 * (fl[1] + fr[1]) - 0.5 * dissipation[1],
709 0.5 * (fl[2] + fr[2]) - 0.5 * dissipation[2],
710 ]
711}
712
713pub fn hll_flux(u_l: f64, u_r: f64, f_l: f64, f_r: f64, s_l: f64, s_r: f64) -> f64 {
722 if s_l >= 0.0 {
723 f_l
724 } else if s_r <= 0.0 {
725 f_r
726 } else {
727 (s_r * f_l - s_l * f_r + s_l * s_r * (u_r - u_l)) / (s_r - s_l)
728 }
729}
730
731pub fn hll_wave_speeds(u_l: f64, a_l: f64, u_r: f64, a_r: f64) -> (f64, f64) {
735 let s_l = (u_l - a_l).min(u_r - a_r);
736 let s_r = (u_l + a_l).max(u_r + a_r);
737 (s_l, s_r)
738}
739
740pub fn hll_flux_euler_1d(
744 rho_l: f64,
745 u_l: f64,
746 p_l: f64,
747 rho_r: f64,
748 u_r: f64,
749 p_r: f64,
750 gamma: f64,
751) -> [f64; 3] {
752 let a_l = (gamma * p_l / rho_l).sqrt().max(0.0);
753 let a_r = (gamma * p_r / rho_r).sqrt().max(0.0);
754 let (s_l, s_r) = hll_wave_speeds(u_l, a_l, u_r, a_r);
755
756 let e_l = p_l / (gamma - 1.0) + 0.5 * rho_l * u_l * u_l;
757 let e_r = p_r / (gamma - 1.0) + 0.5 * rho_r * u_r * u_r;
758
759 let fl = [rho_l * u_l, rho_l * u_l * u_l + p_l, (e_l + p_l) * u_l];
760 let fr = [rho_r * u_r, rho_r * u_r * u_r + p_r, (e_r + p_r) * u_r];
761 let ul = [rho_l, rho_l * u_l, e_l];
762 let ur = [rho_r, rho_r * u_r, e_r];
763
764 let mut f = [0.0f64; 3];
765 for i in 0..3 {
766 f[i] = hll_flux(ul[i], ur[i], fl[i], fr[i], s_l, s_r);
767 }
768 f
769}
770
771pub fn hllc_flux_euler_1d(
780 rho_l: f64,
781 u_l: f64,
782 p_l: f64,
783 rho_r: f64,
784 u_r: f64,
785 p_r: f64,
786 gamma: f64,
787) -> [f64; 3] {
788 let a_l = (gamma * p_l / rho_l.max(1e-30)).sqrt();
789 let a_r = (gamma * p_r / rho_r.max(1e-30)).sqrt();
790
791 let (s_l, s_r) = hll_wave_speeds(u_l, a_l, u_r, a_r);
793
794 let s_star = (p_r - p_l + rho_l * u_l * (s_l - u_l) - rho_r * u_r * (s_r - u_r))
796 / (rho_l * (s_l - u_l) - rho_r * (s_r - u_r) + 1e-200);
797
798 let e_l = p_l / (gamma - 1.0) + 0.5 * rho_l * u_l * u_l;
799 let e_r = p_r / (gamma - 1.0) + 0.5 * rho_r * u_r * u_r;
800
801 let fl = [rho_l * u_l, rho_l * u_l * u_l + p_l, (e_l + p_l) * u_l];
802 let fr = [rho_r * u_r, rho_r * u_r * u_r + p_r, (e_r + p_r) * u_r];
803
804 let hllc_flux_side = |rho: f64, u: f64, e: f64, p: f64, f: &[f64; 3], s: f64| -> [f64; 3] {
805 let coeff = rho * (s - u) / (s - s_star + 1e-200);
806 let u_star = [
807 coeff,
808 coeff * s_star,
809 coeff * (e / rho + (s_star - u) * (s_star + p / (rho * (s - u) + 1e-200))),
810 ];
811 [
812 f[0] + s * (u_star[0] - rho),
813 f[1] + s * (u_star[1] - rho * u),
814 f[2] + s * (u_star[2] - e),
815 ]
816 };
817
818 if s_l >= 0.0 {
819 fl
820 } else if s_star >= 0.0 {
821 hllc_flux_side(rho_l, u_l, e_l, p_l, &fl, s_l)
822 } else if s_r >= 0.0 {
823 hllc_flux_side(rho_r, u_r, e_r, p_r, &fr, s_r)
824 } else {
825 fr
826 }
827}
828
829pub fn characteristic_decompose_2wave(u0: f64, u1: f64, a: f64) -> (f64, f64) {
839 let w_plus = 0.5 * (u0 + u1 / a);
840 let w_minus = 0.5 * (u0 - u1 / a);
841 (w_plus, w_minus)
842}
843
844pub fn characteristic_recompose_2wave(w_plus: f64, w_minus: f64, a: f64) -> (f64, f64) {
846 let u0 = w_plus + w_minus;
847 let u1 = a * (w_plus - w_minus);
848 (u0, u1)
849}
850
851pub fn characteristic_limited_reconstruct(
857 u0: &[f64],
858 u1: &[f64],
859 i: usize,
860 a: f64,
861 limiter: fn(f64, f64) -> f64,
862) -> ([f64; 2], [f64; 2]) {
863 let n = u0.len();
864 let get = |arr: &[f64], j: usize| -> f64 { arr[j.min(n - 1)] };
865
866 let (wm_im, _) = characteristic_decompose_2wave(
868 get(u0, i.saturating_sub(1)),
869 get(u1, i.saturating_sub(1)),
870 a,
871 );
872 let (wm_i, wp_i) = characteristic_decompose_2wave(get(u0, i), get(u1, i), a);
873 let (wm_ip, wp_ip) = characteristic_decompose_2wave(get(u0, i + 1), get(u1, i + 1), a);
874 let (wm_ipp, wp_ipp) = if i + 2 < n {
875 characteristic_decompose_2wave(get(u0, i + 2), get(u1, i + 2), a)
876 } else {
877 (wm_ip, wp_ip)
878 };
879 let (_, wp_im) = characteristic_decompose_2wave(
880 get(u0, i.saturating_sub(1)),
881 get(u1, i.saturating_sub(1)),
882 a,
883 );
884
885 let sig_p_l = limiter(wp_i - wp_im, wp_ip - wp_i);
887 let sig_p_r = limiter(wp_ip - wp_i, wp_ipp - wp_ip);
888 let sig_m_l = limiter(wm_i - wm_im, wm_ip - wm_i);
889 let sig_m_r = limiter(wm_ip - wm_i, wm_ipp - wm_ip);
890
891 let wp_l = wp_i + 0.5 * sig_p_l;
892 let wp_r = wp_ip - 0.5 * sig_p_r;
893 let wm_l = wm_i + 0.5 * sig_m_l;
894 let wm_r = wm_ip - 0.5 * sig_m_r;
895
896 let (u0_l, u1_l) = characteristic_recompose_2wave(wp_l, wm_l, a);
897 let (u0_r, u1_r) = characteristic_recompose_2wave(wp_r, wm_r, a);
898
899 ([u0_l, u1_l], [u0_r, u1_r])
900}
901
902pub fn tvd_rk2_advect(phi: &FluxGrid3D, vel: &VectorFluxGrid3D, dt: f64) -> FluxGrid3D {
914 let phi_1 = advect_upwind_3d(phi, vel, dt);
916 let phi_2 = advect_upwind_3d(&phi_1, vel, dt);
918 let mut result = phi.clone();
920 result
921 .values
922 .iter_mut()
923 .zip(phi.values.iter().zip(phi_2.values.iter()))
924 .for_each(|(r, (&a, &b))| *r = 0.5 * (a + b));
925 result
926}
927
928pub fn minmod_ratio(r: f64) -> f64 {
936 if r <= 0.0 {
937 0.0
938 } else if r <= 1.0 {
939 r
940 } else {
941 1.0
942 }
943}
944
945pub fn superbee_ratio(r: f64) -> f64 {
947 if r <= 0.0 {
948 0.0
949 } else {
950 (2.0 * r).min(1.0_f64).max(r.min(2.0_f64)).max(0.0_f64)
951 }
952}
953
954pub fn van_leer_ratio(r: f64) -> f64 {
956 (r + r.abs()) / (1.0 + r.abs())
957}
958
959#[cfg(test)]
964mod flux_compute_tests {
965 use super::*;
966
967 #[test]
968 fn test_flux_grid3d_get_set() {
969 let mut g = FluxGrid3D::new(4, 4, 4, 0.1, 0.0);
970 g.set(1, 2, 3, 7.5);
971 assert!((g.get(1, 2, 3) - 7.5).abs() < 1e-12);
972 }
973
974 #[test]
975 fn test_flux_grid3d_clamped_boundary() {
976 let g = FluxGrid3D::new(5, 5, 5, 0.1, 3.0);
977 assert!((g.get_clamped(-1, 0, 0) - 3.0).abs() < 1e-12);
979 assert!((g.get_clamped(100, 0, 0) - 3.0).abs() < 1e-12);
980 }
981
982 #[test]
983 fn test_l2_norm_uniform() {
984 let g = FluxGrid3D::new(2, 2, 2, 0.5, 1.0);
986 let expected = (8.0_f64).sqrt();
987 assert!((g.l2_norm() - expected).abs() < 1e-10);
988 }
989
990 #[test]
991 fn test_minmod_same_sign() {
992 assert!(
993 (minmod(2.0, 3.0) - 2.0).abs() < 1e-12,
994 "minmod picks smaller"
995 );
996 assert!((minmod(-1.0, -4.0) - (-1.0)).abs() < 1e-12);
997 }
998
999 #[test]
1000 fn test_minmod_opposite_sign() {
1001 assert!(
1002 (minmod(2.0, -1.0)).abs() < 1e-12,
1003 "minmod returns 0 for opposite signs"
1004 );
1005 }
1006
1007 #[test]
1008 fn test_van_leer_smooth() {
1009 let vl = van_leer(2.0, 2.0);
1011 assert!(
1012 (vl - 2.0).abs() < 1e-12,
1013 "symmetric inputs: van_leer = value"
1014 );
1015 let vl2 = van_leer(1.0, 3.0);
1016 let expected = 2.0 * 1.0 * 3.0 / (1.0 + 3.0);
1017 assert!((vl2 - expected).abs() < 1e-12);
1018 }
1019
1020 #[test]
1021 fn test_upwind_flux_1d_positive_velocity() {
1022 let phi = vec![1.0; 10];
1024 let flux = upwind_flux_1d(&phi, 1.0, 0.1, 0.05);
1025 for f in &flux {
1026 assert!(
1027 f.abs() < 1e-12,
1028 "uniform field with positive vel: zero flux"
1029 );
1030 }
1031 }
1032
1033 #[test]
1034 fn test_lax_friedrichs_1d_preserves_mass() {
1035 let u: Vec<f64> = (0..20).map(|i| (i as f64 * 0.1 - 1.0).powi(2)).collect();
1038 let sum_before: f64 = u.iter().sum();
1039 let u_new = lax_friedrichs_advect_1d(&u, 0.5, 0.1, 0.01);
1040 let sum_after: f64 = u_new.iter().sum();
1041 assert!(
1043 (sum_after - sum_before).abs() < sum_before * 0.05 + 1.0,
1044 "mass should be approximately conserved"
1045 );
1046 }
1047
1048 #[test]
1049 fn test_divergence_constant_field_is_zero() {
1050 let n = 6;
1052 let mut vel = VectorFluxGrid3D::zeros(n, n, n, 0.1);
1053 for i in 0..n {
1054 for j in 0..n {
1055 for k in 0..n {
1056 vel.set_vel(i, j, k, [1.0, 2.0, 3.0]);
1057 }
1058 }
1059 }
1060 let div = divergence_3d(&vel);
1061 for &d in &div.values {
1062 assert!(
1063 d.abs() < 1e-10,
1064 "divergence of uniform field must be 0, got {d}"
1065 );
1066 }
1067 }
1068
1069 #[test]
1070 fn test_gradient_linear_field() {
1071 let n = 8;
1073 let dx = 0.25;
1074 let mut phi = FluxGrid3D::new(n, n, n, dx, 0.0);
1075 for i in 0..n {
1076 for j in 0..n {
1077 for k in 0..n {
1078 phi.set(i, j, k, i as f64 * dx);
1079 }
1080 }
1081 }
1082 let grad = gradient_3d(&phi);
1083 for i in 1..n - 1 {
1085 for j in 1..n - 1 {
1086 for k in 1..n - 1 {
1087 let gx = grad.u.get(i, j, k);
1088 assert!(
1089 (gx - 1.0).abs() < 1e-10,
1090 "gradient_x of linear field should be 1, got {gx} at ({i},{j},{k})"
1091 );
1092 }
1093 }
1094 }
1095 }
1096
1097 #[test]
1098 fn test_cfl_dt_uniform_velocity() {
1099 let mut vel = VectorFluxGrid3D::zeros(4, 4, 4, 0.1);
1100 for i in 0..4 {
1101 for j in 0..4 {
1102 for k in 0..4 {
1103 vel.set_vel(i, j, k, [2.0, 0.0, 0.0]);
1104 }
1105 }
1106 }
1107 let dt = cfl_dt(&vel, 0.5);
1109 assert!(
1110 (dt - 0.025).abs() < 1e-10,
1111 "cfl_dt should be 0.025, got {dt}"
1112 );
1113 }
1114
1115 #[test]
1116 fn test_cfl_dt_zero_velocity() {
1117 let vel = VectorFluxGrid3D::zeros(4, 4, 4, 0.1);
1118 let dt = cfl_dt(&vel, 0.5);
1119 assert!(dt.is_infinite(), "zero velocity → infinite dt");
1120 }
1121
1122 #[test]
1125 fn test_flux_grid3d_len_empty() {
1126 let g = FluxGrid3D::new(0, 5, 5, 0.1, 0.0);
1127 assert_eq!(g.len(), 0);
1128 assert!(g.is_empty());
1129 }
1130
1131 #[test]
1132 fn test_flux_grid3d_len_nonempty() {
1133 let g = FluxGrid3D::new(3, 4, 5, 0.1, 0.0);
1134 assert_eq!(g.len(), 60);
1135 assert!(!g.is_empty());
1136 }
1137
1138 #[test]
1139 fn test_flux_grid3d_max_val() {
1140 let mut g = FluxGrid3D::new(2, 2, 2, 0.1, 0.0);
1141 g.set(0, 0, 0, 5.0);
1142 g.set(1, 1, 1, -3.0);
1143 assert!((g.max_val() - 5.0).abs() < 1e-12);
1144 }
1145
1146 #[test]
1147 fn test_flux_grid3d_min_val() {
1148 let mut g = FluxGrid3D::new(2, 2, 2, 0.1, 0.0);
1149 g.set(0, 0, 0, 5.0);
1150 g.set(1, 1, 1, -3.0);
1151 assert!((g.min_val() - (-3.0)).abs() < 1e-12);
1152 }
1153
1154 #[test]
1155 fn test_flux_grid3d_l2_norm_zeros() {
1156 let g = FluxGrid3D::new(3, 3, 3, 0.1, 0.0);
1157 assert!(g.l2_norm() < 1e-15);
1158 }
1159
1160 #[test]
1161 fn test_flux_grid3d_index_layout() {
1162 let g = FluxGrid3D::new(3, 4, 5, 0.1, 0.0);
1163 assert_eq!(g.idx(0, 0, 0), 0);
1165 assert_eq!(g.idx(1, 0, 0), 20); assert_eq!(g.idx(0, 1, 0), 5); assert_eq!(g.idx(0, 0, 1), 1);
1168 }
1169
1170 #[test]
1171 fn test_flux_grid3d_clamped_low_indices() {
1172 let mut g = FluxGrid3D::new(5, 5, 5, 0.1, 0.0);
1173 g.set(0, 0, 0, 99.0);
1174 assert!((g.get_clamped(-1, 0, 0) - 99.0).abs() < 1e-12);
1176 assert!((g.get_clamped(0, -5, 0) - 99.0).abs() < 1e-12);
1177 }
1178
1179 #[test]
1182 fn test_vector_flux_grid_zeros() {
1183 let v = VectorFluxGrid3D::zeros(3, 3, 3, 0.1);
1184 for i in 0..3 {
1185 for j in 0..3 {
1186 for k in 0..3 {
1187 let vel = v.get_vel(i, j, k);
1188 assert_eq!(vel, [0.0, 0.0, 0.0]);
1189 }
1190 }
1191 }
1192 }
1193
1194 #[test]
1195 fn test_vector_flux_grid_set_get_vel() {
1196 let mut v = VectorFluxGrid3D::zeros(4, 4, 4, 0.1);
1197 v.set_vel(1, 2, 3, [1.5, 2.5, 3.5]);
1198 let vel = v.get_vel(1, 2, 3);
1199 assert!((vel[0] - 1.5).abs() < 1e-12);
1200 assert!((vel[1] - 2.5).abs() < 1e-12);
1201 assert!((vel[2] - 3.5).abs() < 1e-12);
1202 }
1203
1204 #[test]
1207 fn test_superbee_same_sign_positive() {
1208 let s = superbee(1.0, 2.0);
1210 assert!(s > 0.0, "superbee of positives should be positive, got {s}");
1211 }
1212
1213 #[test]
1214 fn test_superbee_opposite_signs() {
1215 assert_eq!(superbee(1.0, -1.0), 0.0);
1216 assert_eq!(superbee(-2.0, 3.0), 0.0);
1217 }
1218
1219 #[test]
1220 fn test_superbee_both_zero() {
1221 assert_eq!(superbee(0.0, 0.0), 0.0);
1222 }
1223
1224 #[test]
1225 fn test_mc_limiter_same_sign() {
1226 let mc = mc_limiter(2.0, 4.0);
1227 assert!(mc > 0.0 && mc <= 3.0, "mc_limiter = {mc}");
1229 }
1230
1231 #[test]
1232 fn test_mc_limiter_opposite_sign() {
1233 assert_eq!(mc_limiter(1.0, -1.0), 0.0);
1234 }
1235
1236 #[test]
1237 fn test_van_leer_zero_sum() {
1238 let vl = van_leer(2.0, -2.0);
1241 assert_eq!(vl, 0.0);
1242 }
1243
1244 #[test]
1245 fn test_minmod_equal_values() {
1246 assert!((minmod(3.0, 3.0) - 3.0).abs() < 1e-12);
1248 assert!((minmod(-2.0, -2.0) - (-2.0)).abs() < 1e-12);
1249 }
1250
1251 #[test]
1254 fn test_upwind_flux_1d_negative_velocity() {
1255 let phi = vec![1.0; 8];
1257 let flux = upwind_flux_1d(&phi, -1.0, 0.1, 0.05);
1258 for f in &flux {
1259 assert!(
1260 f.abs() < 1e-12,
1261 "uniform field with negative vel: zero flux"
1262 );
1263 }
1264 }
1265
1266 #[test]
1267 fn test_upwind_flux_1d_step_function() {
1268 let phi = vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0];
1270 let flux = upwind_flux_1d(&phi, 1.0, 0.1, 0.01);
1271 assert!(
1273 flux[3] < 0.0,
1274 "step up with positive vel: negative flux at boundary"
1275 );
1276 }
1277
1278 #[test]
1279 fn test_upwind_flux_1d_length() {
1280 let phi = vec![1.0; 7];
1281 let flux = upwind_flux_1d(&phi, 1.0, 0.1, 0.01);
1282 assert_eq!(flux.len(), 7);
1283 }
1284
1285 #[test]
1288 fn test_lax_friedrichs_flux_zero_difference() {
1289 let f = lax_friedrichs_flux(1.0, 1.0, 2.0, 2.0, 1.0);
1291 assert!((f - 2.0).abs() < 1e-12, "LF flux = {f}");
1292 }
1293
1294 #[test]
1295 fn test_lax_friedrichs_advect_1d_empty() {
1296 let result = lax_friedrichs_advect_1d(&[], 1.0, 0.1, 0.01);
1297 assert!(result.is_empty());
1298 }
1299
1300 #[test]
1301 fn test_lax_friedrichs_advect_1d_length() {
1302 let u = vec![1.0; 6];
1303 let u_new = lax_friedrichs_advect_1d(&u, 0.5, 0.1, 0.01);
1304 assert_eq!(u_new.len(), 6);
1305 }
1306
1307 #[test]
1308 fn test_lax_friedrichs_uniform_field_stable() {
1309 let u = vec![1.0; 10];
1311 let u_new = lax_friedrichs_advect_1d(&u, 1.0, 0.1, 0.05);
1312 let diff: f64 = u_new.iter().zip(u.iter()).map(|(a, b)| (a - b).abs()).sum();
1313 assert!(
1314 diff < 1e-10,
1315 "uniform field should not change under LF advection"
1316 );
1317 }
1318
1319 #[test]
1322 fn test_divergence_size_matches_input() {
1323 let vel = VectorFluxGrid3D::zeros(3, 4, 5, 0.1);
1324 let div = divergence_3d(&vel);
1325 assert_eq!(div.nx, 3);
1326 assert_eq!(div.ny, 4);
1327 assert_eq!(div.nz, 5);
1328 }
1329
1330 #[test]
1331 fn test_gradient_size_matches_input() {
1332 let phi = FluxGrid3D::new(3, 4, 5, 0.1, 0.0);
1333 let grad = gradient_3d(&phi);
1334 assert_eq!(grad.u.nx, 3);
1335 assert_eq!(grad.v.ny, 4);
1336 assert_eq!(grad.w.nz, 5);
1337 }
1338
1339 #[test]
1340 fn test_gradient_constant_field_is_zero() {
1341 let phi = FluxGrid3D::new(5, 5, 5, 0.1, 7.0);
1342 let grad = gradient_3d(&phi);
1343 for &g in &grad.u.values {
1344 assert!(g.abs() < 1e-10, "gradient of constant = 0");
1345 }
1346 }
1347
1348 #[test]
1349 fn test_divergence_linear_vx_field() {
1350 let n = 8;
1352 let dx = 0.5;
1353 let mut vel = VectorFluxGrid3D::zeros(n, n, n, dx);
1354 for i in 0..n {
1355 for j in 0..n {
1356 for k in 0..n {
1357 vel.u.set(i, j, k, i as f64 * dx);
1358 }
1359 }
1360 }
1361 let div = divergence_3d(&vel);
1362 for i in 1..n - 1 {
1364 for j in 1..n - 1 {
1365 for k in 1..n - 1 {
1366 let d = div.get(i, j, k);
1367 assert!(
1368 (d - 1.0).abs() < 1e-10,
1369 "div at ({i},{j},{k}) = {d}, expected 1"
1370 );
1371 }
1372 }
1373 }
1374 }
1375
1376 #[test]
1379 fn test_euler_step_advect_preserves_size() {
1380 let phi = FluxGrid3D::new(4, 4, 4, 0.1, 1.0);
1381 let mut vel = VectorFluxGrid3D::zeros(4, 4, 4, 0.1);
1382 for i in 0..4 {
1383 for j in 0..4 {
1384 for k in 0..4 {
1385 vel.set_vel(i, j, k, [1.0, 0.0, 0.0]);
1386 }
1387 }
1388 }
1389 let phi_new = euler_step_advect(&phi, &vel, 0.01);
1390 assert_eq!(phi_new.nx, phi.nx);
1391 assert_eq!(phi_new.ny, phi.ny);
1392 assert_eq!(phi_new.nz, phi.nz);
1393 }
1394
1395 #[test]
1396 fn test_euler_step_constant_phi_no_change() {
1397 let phi = FluxGrid3D::new(5, 5, 5, 0.1, 3.0);
1399 let mut vel = VectorFluxGrid3D::zeros(5, 5, 5, 0.1);
1400 for i in 0..5 {
1401 for j in 0..5 {
1402 for k in 0..5 {
1403 vel.set_vel(i, j, k, [1.0, 1.0, 1.0]);
1404 }
1405 }
1406 }
1407 let phi_new = euler_step_advect(&phi, &vel, 0.01);
1408 for (&a, &b) in phi.values.iter().zip(phi_new.values.iter()) {
1409 assert!(
1410 (a - b).abs() < 1e-10,
1411 "constant phi should not change: {a} vs {b}"
1412 );
1413 }
1414 }
1415
1416 #[test]
1419 fn test_cfl_dt_negative_velocity() {
1420 let mut vel = VectorFluxGrid3D::zeros(4, 4, 4, 0.2);
1422 vel.set_vel(0, 0, 0, [0.0, -4.0, 0.0]);
1423 let dt = cfl_dt(&vel, 1.0);
1424 assert!((dt - 0.05).abs() < 1e-10, "cfl_dt = {dt}, expected 0.05");
1425 }
1426
1427 #[test]
1428 fn test_cfl_dt_multiple_components() {
1429 let mut vel = VectorFluxGrid3D::zeros(3, 3, 3, 0.1);
1430 vel.set_vel(1, 1, 1, [1.0, 2.0, 3.0]);
1432 let dt = cfl_dt(&vel, 1.0);
1433 assert!((dt - 0.1 / 3.0).abs() < 1e-10, "cfl_dt = {dt}");
1434 }
1435
1436 #[test]
1439 fn test_euler_state_from_primitives() {
1440 let gamma = 1.4;
1441 let s = EulerState::from_primitives(1.0, 0.1, 0.0, 0.0, 1.0, gamma);
1442 assert!((s.rho - 1.0).abs() < 1e-12);
1443 let p_back = s.pressure(gamma);
1444 assert!((p_back - 1.0).abs() < 1e-10, "pressure roundtrip: {p_back}");
1445 }
1446
1447 #[test]
1448 fn test_euler_state_velocity() {
1449 let gamma = 1.4;
1450 let s = EulerState::from_primitives(2.0, 3.0, 4.0, 5.0, 1.0, gamma);
1451 let u = s.velocity();
1452 assert!((u[0] - 3.0).abs() < 1e-12);
1453 assert!((u[1] - 4.0).abs() < 1e-12);
1454 assert!((u[2] - 5.0).abs() < 1e-12);
1455 }
1456
1457 #[test]
1458 fn test_euler_state_sound_speed() {
1459 let gamma = 1.4;
1460 let s = EulerState::from_primitives(1.0, 0.0, 0.0, 0.0, 1.0, gamma);
1462 let c = s.sound_speed(gamma);
1463 let expected = (gamma).sqrt();
1464 assert!(
1465 (c - expected).abs() < 1e-10,
1466 "sound speed = {c}, expected {expected}"
1467 );
1468 }
1469
1470 #[test]
1471 fn test_euler_state_lerp() {
1472 let gamma = 1.4;
1473 let s0 = EulerState::from_primitives(1.0, 0.0, 0.0, 0.0, 1.0, gamma);
1474 let s1 = EulerState::from_primitives(2.0, 0.0, 0.0, 0.0, 2.0, gamma);
1475 let s05 = s0.lerp(&s1, 0.5);
1476 assert!((s05.rho - 1.5).abs() < 1e-12, "lerp rho = {}", s05.rho);
1477 }
1478
1479 #[test]
1480 fn test_euler_state_flux_x() {
1481 let gamma = 1.4;
1482 let s = EulerState::from_primitives(1.0, 0.0, 0.0, 0.0, 1.0, gamma);
1484 let f = s.flux_x(gamma);
1485 assert!(f[0].abs() < 1e-12, "mass flux at rest = {}", f[0]);
1486 assert!((f[1] - 1.0).abs() < 1e-10, "pressure flux = {}", f[1]);
1487 }
1488
1489 #[test]
1492 fn test_van_albada_same_sign() {
1493 let v = van_albada(2.0, 4.0);
1495 assert!(v > 0.0, "van_albada(2,4) = {v}");
1496 assert!(v <= 4.0);
1498 }
1499
1500 #[test]
1501 fn test_van_albada_opposite_signs() {
1502 assert_eq!(van_albada(1.0, -1.0), 0.0);
1503 assert_eq!(van_albada(-2.0, 3.0), 0.0);
1504 }
1505
1506 #[test]
1507 fn test_van_albada_symmetry() {
1508 let v1 = van_albada(1.0, 3.0);
1510 let v2 = van_albada(3.0, 1.0);
1511 assert!(v1 > 0.0 && v2 > 0.0);
1512 }
1513
1514 #[test]
1517 fn test_muscl_reconstruct_uniform() {
1518 let u = vec![1.0_f64; 10];
1520 let (u_l, u_r) = muscl_reconstruct(&u, 3, minmod);
1521 assert!((u_l - 1.0).abs() < 1e-12, "uniform MUSCL u_l = {u_l}");
1522 assert!((u_r - 1.0).abs() < 1e-12, "uniform MUSCL u_r = {u_r}");
1523 }
1524
1525 #[test]
1526 fn test_muscl_reconstruct_all_length() {
1527 let u = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1528 let pairs = muscl_reconstruct_all(&u, minmod);
1529 assert_eq!(pairs.len(), 4, "MUSCL all: expected 4 interfaces");
1530 }
1531
1532 #[test]
1533 fn test_muscl_reconstruct_all_monotone() {
1534 let u: Vec<f64> = (0..8).map(|i| i as f64).collect();
1536 let pairs = muscl_reconstruct_all(&u, minmod);
1537 for (i, &(u_l, u_r)) in pairs.iter().enumerate() {
1538 assert!(u_l <= u_r + 1e-10, "interface {i}: u_l={u_l} > u_r={u_r}");
1539 }
1540 }
1541
1542 #[test]
1545 fn test_godunov_flux_advection_positive_speed() {
1546 let f = godunov_flux_advection(2.0, 3.0, 1.0);
1548 assert!((f - 2.0).abs() < 1e-12, "Godunov advection +speed: {f}");
1549 }
1550
1551 #[test]
1552 fn test_godunov_flux_advection_negative_speed() {
1553 let f = godunov_flux_advection(2.0, 3.0, -1.0);
1554 assert!((f - (-3.0)).abs() < 1e-12, "Godunov advection -speed: {f}");
1555 }
1556
1557 #[test]
1558 fn test_godunov_flux_burgers_shock() {
1559 let f = godunov_flux_burgers(2.0, 1.0);
1561 assert!((f - 0.5 * 4.0).abs() < 1e-12, "Burgers shock: {f}");
1562 }
1563
1564 #[test]
1565 fn test_godunov_flux_burgers_rarefaction_sonic() {
1566 let f = godunov_flux_burgers(-1.0, 1.0);
1568 assert!(f.abs() < 1e-12, "Burgers sonic: {f}");
1569 }
1570
1571 #[test]
1572 fn test_godunov_flux_burgers_rarefaction_positive() {
1573 let f = godunov_flux_burgers(1.0, 2.0);
1575 assert!((f - 0.5).abs() < 1e-12, "Burgers pos rarefaction: {f}");
1576 }
1577
1578 #[test]
1581 fn test_roe_flux_scalar_symmetry() {
1582 let f = roe_flux_scalar(2.0, 2.0, 1.5);
1584 assert!((f - 1.5 * 2.0).abs() < 1e-12, "Roe scalar uniform: {f}");
1585 }
1586
1587 #[test]
1588 fn test_roe_flux_scalar_upwind() {
1589 let f = roe_flux_scalar(1.0, 2.0, 1.0);
1591 assert!((f - 1.0).abs() < 1e-12, "Roe scalar upwind: {f}");
1593 }
1594
1595 #[test]
1596 fn test_roe_flux_euler_1d_finite() {
1597 let f = roe_flux_euler_1d(1.0, 0.1, 1.0, 1.0, 0.1, 1.0, 1.4);
1598 for (i, &fi) in f.iter().enumerate() {
1599 assert!(fi.is_finite(), "Roe Euler flux[{i}] not finite: {fi}");
1600 }
1601 }
1602
1603 #[test]
1606 fn test_hll_flux_subsonic() {
1607 let f = hll_flux(1.0, 2.0, 1.0, 2.0, -1.0, 1.0);
1609 assert!(f.is_finite(), "HLL subsonic: {f}");
1610 }
1611
1612 #[test]
1613 fn test_hll_flux_supersonic_right() {
1614 let f = hll_flux(1.0, 2.0, 3.0, 4.0, 1.0, 2.0);
1616 assert!((f - 3.0).abs() < 1e-12, "HLL supersonic right: {f}");
1617 }
1618
1619 #[test]
1620 fn test_hll_flux_supersonic_left() {
1621 let f = hll_flux(1.0, 2.0, 3.0, 4.0, -2.0, -1.0);
1623 assert!((f - 4.0).abs() < 1e-12, "HLL supersonic left: {f}");
1624 }
1625
1626 #[test]
1627 fn test_hll_flux_euler_1d_finite() {
1628 let f = hll_flux_euler_1d(1.0, 0.1, 1.0, 1.2, 0.05, 1.1, 1.4);
1629 for &fi in &f {
1630 assert!(fi.is_finite(), "HLL Euler flux not finite");
1631 }
1632 }
1633
1634 #[test]
1635 fn test_hll_wave_speeds() {
1636 let (s_l, s_r) = hll_wave_speeds(0.5, 1.0, -0.5, 1.0);
1637 assert!(s_l < 0.0 && s_r > 0.0, "wave speeds s_l={s_l} s_r={s_r}");
1638 assert!(s_l <= s_r);
1639 }
1640
1641 #[test]
1644 fn test_hllc_flux_euler_1d_finite() {
1645 let f = hllc_flux_euler_1d(1.0, 0.1, 1.0, 1.2, 0.05, 1.1, 1.4);
1646 for &fi in &f {
1647 assert!(fi.is_finite(), "HLLC Euler flux not finite: {fi}");
1648 }
1649 }
1650
1651 #[test]
1652 fn test_hllc_agrees_with_hll_at_uniform_state() {
1653 let f_hll = hll_flux_euler_1d(1.0, 0.1, 1.0, 1.0, 0.1, 1.0, 1.4);
1655 let f_hllc = hllc_flux_euler_1d(1.0, 0.1, 1.0, 1.0, 0.1, 1.0, 1.4);
1656 for i in 0..3 {
1657 assert!(
1658 (f_hll[i] - f_hllc[i]).abs() < 1e-6,
1659 "HLL vs HLLC at [{}]: {} vs {}",
1660 i,
1661 f_hll[i],
1662 f_hllc[i]
1663 );
1664 }
1665 }
1666
1667 #[test]
1670 fn test_characteristic_roundtrip() {
1671 let u0 = 1.5;
1672 let u1 = 0.7;
1673 let a = 2.0;
1674 let (wp, wm) = characteristic_decompose_2wave(u0, u1, a);
1675 let (u0_back, u1_back) = characteristic_recompose_2wave(wp, wm, a);
1676 assert!((u0_back - u0).abs() < 1e-12, "roundtrip u0: {u0_back}");
1677 assert!((u1_back - u1).abs() < 1e-12, "roundtrip u1: {u1_back}");
1678 }
1679
1680 #[test]
1681 fn test_characteristic_limited_reconstruct_finite() {
1682 let u0: Vec<f64> = (0..8).map(|i| i as f64 * 0.1).collect();
1683 let u1: Vec<f64> = vec![0.5; 8];
1684 let (left, right) = characteristic_limited_reconstruct(&u0, &u1, 3, 1.0, minmod);
1685 for &v in left.iter().chain(right.iter()) {
1686 assert!(v.is_finite(), "characteristic reconstruct not finite: {v}");
1687 }
1688 }
1689
1690 #[test]
1693 fn test_tvd_rk2_advect_uniform_unchanged() {
1694 let phi = FluxGrid3D::new(5, 5, 5, 0.1, 2.0);
1695 let mut vel = VectorFluxGrid3D::zeros(5, 5, 5, 0.1);
1696 for i in 0..5 {
1697 for j in 0..5 {
1698 for k in 0..5 {
1699 vel.set_vel(i, j, k, [1.0, 0.0, 0.0]);
1700 }
1701 }
1702 }
1703 let phi_new = tvd_rk2_advect(&phi, &vel, 0.01);
1704 for (&a, &b) in phi.values.iter().zip(phi_new.values.iter()) {
1705 assert!((a - b).abs() < 1e-10, "TVD RK2 uniform: {a} vs {b}");
1706 }
1707 }
1708
1709 #[test]
1710 fn test_tvd_rk2_advect_size_preserved() {
1711 let phi = FluxGrid3D::new(4, 3, 2, 0.1, 1.0);
1712 let vel = VectorFluxGrid3D::zeros(4, 3, 2, 0.1);
1713 let phi_new = tvd_rk2_advect(&phi, &vel, 0.01);
1714 assert_eq!(phi_new.nx, phi.nx);
1715 assert_eq!(phi_new.ny, phi.ny);
1716 assert_eq!(phi_new.nz, phi.nz);
1717 }
1718
1719 #[test]
1722 fn test_minmod_ratio_clamp() {
1723 assert_eq!(minmod_ratio(-0.5), 0.0);
1724 assert!((minmod_ratio(0.5) - 0.5).abs() < 1e-12);
1725 assert!((minmod_ratio(2.0) - 1.0).abs() < 1e-12);
1726 }
1727
1728 #[test]
1729 fn test_superbee_ratio_basic() {
1730 assert_eq!(superbee_ratio(-1.0), 0.0);
1731 let v = superbee_ratio(1.5);
1732 assert!(v > 0.0 && v <= 2.0, "superbee_ratio(1.5) = {v}");
1733 }
1734
1735 #[test]
1736 fn test_van_leer_ratio_basic() {
1737 let v = van_leer_ratio(1.0);
1739 assert!((v - 1.0).abs() < 1e-12, "van_leer_ratio(1) = {v}");
1740 assert_eq!(van_leer_ratio(-0.5), 0.0);
1742 }
1743
1744 #[test]
1745 fn test_van_leer_ratio_monotone() {
1746 let v1 = van_leer_ratio(0.5);
1748 let v2 = van_leer_ratio(1.0);
1749 let v3 = van_leer_ratio(2.0);
1750 assert!(v1 <= v2, "van_leer not monotone: {v1} > {v2}");
1751 assert!(v2 <= v3 + 1e-12, "van_leer not monotone: {v2} > {v3}");
1752 }
1753}