flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
verify_velocity_mg_upwind_cavity_sdflow.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Upwind-convective velocity-MG on the DOMAIN-BC path (lid-driven cavity, Ghia Re=100), task #56.
3
4This exercises the implicit-FOU advection + upwind-convective velocity multigrid on a problem with native
5domain boundary conditions (no immersed solid) -- the cavity. The point is the CFL >> 1 regime: with
6EXPLICIT advection a large dt is unstable (advective CFL = U*dt/dx); the implicit-FOU deferred correction
7solves the first-order-upwind part implicitly (every MG level an M-matrix -> unconditionally stable for
8advection) and keeps the (Koren - FOU) correction explicit, so the scheme is still Koren TVD at steady.
9
10Checks:
11 (1) reference: explicit advection at a SMALL dt converges to Ghia (ground truth);
12 (2) at a LARGE dt (CFL >> 1): explicit BLOWS UP, while implicit-FOU + upwind vmg stays bounded and
13 converges to the SAME Ghia centreline -> the upwind coarse operator is stable + correct at high CFL.
14Quasi-2D (nz=4) so semi-coarsening builds a deep velocity-MG hierarchy. One GPU.
15"""
16import os
17import sys
18import pathlib
19
20import numpy as np
21
22sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent /
23 os.environ.get("SDFLOW_BUILD", "build_mpi")))
24from peclet import flow as sdflow # noqa: E402
25sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
26from verify_lid_cavity_sdflow import GHIA_Y, GHIA_U # noqa: E402
27
28
29def run(N=128, Re=100.0, U=1.0, nz=4, dt=1.0, max_steps=6000, implicit=False, vmg=False,
30 vlevels=8, vcycles=4, vel_iter=60, outer=2):
31 nu = U * N / Re
32 s = sdflow.Solver(N, N, nz)
33 s.set_rho(1.0); s.set_mu(nu); s.set_dt(dt); s.set_advection(True)
34 s.set_implicit_advection(implicit)
35 s.set_outer_iterations(outer)
36 s.set_domain_bc(0, 1); s.set_domain_bc(1, 1); s.set_domain_bc(2, 1)
37 s.set_domain_bc(3, 2, U, 0.0, 0.0) # +y face = moving lid
38 s.set_velocity_solver_params(vel_iter)
39 if vmg:
40 s.set_velocity_multigrid(True, vlevels, vcycles)
41 s.set_pressure_multigrid(True, levels=8)
42 s.set_pressure_solver_params(80)
43 s.set_pressure_geometry(np.full((N, N, nz), 1e30))
44 prev = None
45 steps = max_steps
46 for it in range(max_steps):
47 s.step()
48 u = np.asarray(s.get_u()).reshape((N, N, nz), order="F")
49 if not np.isfinite(u).all() or np.abs(u).max() > 1e3:
50 return None # blew up
51 if it % 100 == 0:
52 cur = u[:, :, nz // 2].copy()
53 if prev is not None:
54 d = np.max(np.abs(cur - prev)) / (np.max(np.abs(cur)) + 1e-30)
55 if d < 2e-5:
56 steps = it + 1; break
57 prev = cur
58 u = np.asarray(s.get_u()).reshape((N, N, nz), order="F")
59 uc = 0.5 * (u[N // 2 - 1, :, nz // 2] + u[N // 2, :, nz // 2])
60 yc = (np.arange(N) + 0.5) / N
61 u_rms = float(np.sqrt(np.mean((np.interp(GHIA_Y, yc, uc) - GHIA_U) ** 2)))
62 return dict(u_rms=u_rms, umin=float(uc.min()), div=float(s.max_open_divergence()), steps=steps)
63
64
65def main():
66 N = int(os.environ.get("VMG_N", 64))
67 Re = 100.0
68 print(f"=== upwind velocity-MG on domain-BC (lid cavity Re={Re:.0f}, N={N}, quasi-2D nz=4) ===")
69
70 # (1) ground truth: explicit advection, small dt (stable) -> Ghia. Grid units: dx=1, lid U=1, so the
71 # advective CFL is just U*dt; dt=2 (CFL=2) is comfortably stable.
72 ref = run(N=N, Re=Re, dt=2.0, implicit=False, vmg=False, max_steps=12000)
73 print(f" (1) explicit dt=2 (CFL=2, stable): u_rms={ref['u_rms']:.4f} "
74 f"umin={ref['umin']:.4f} (Ghia -0.2058) div={ref['div']:.1e} steps={ref['steps']}")
75
76 # (2) high CFL: explicit blows up (threshold ~dt=20); implicit-FOU + upwind vmg stays stable + reaches
77 # the same steady state. Grid units dx=1 -> advective CFL = U*dt (here 40).
78 DT = float(os.environ.get("VMG_DT", 40.0))
79 print(f" (2) high CFL: dt={DT:.0f} (advective CFL = U*dt = {DT:.0f})")
80 ex = run(N=N, Re=Re, dt=DT, implicit=False, vmg=False, max_steps=4000)
81 iv = run(N=N, Re=Re, dt=DT, implicit=True, vmg=True, vlevels=8, vcycles=6, max_steps=4000)
82 expl_blew = ex is None
83 vmg_ok = iv is not None
84 print(f" explicit advection : {'BLEW UP' if expl_blew else 'u_rms=%.4f umin=%.4f' % (ex['u_rms'], ex['umin'])}")
85 if vmg_ok:
86 print(f" implicit-FOU + upwind vmg: u_rms={iv['u_rms']:.4f} umin={iv['umin']:.4f} "
87 f"div={iv['div']:.1e} steps={iv['steps']}")
88 else:
89 print(" implicit-FOU + upwind vmg: UNSTABLE")
90
91 # the implicit-FOU vmg solution must match the explicit ground-truth Ghia centreline
92 match = vmg_ok and abs(iv["u_rms"] - ref["u_rms"]) < 0.02 and iv["div"] < 1e-4
93 ok = expl_blew and vmg_ok and match
94 print(f" result: {'PASS' if ok else 'FAIL'} (upwind vmg stable at CFL>>1 where explicit fails; "
95 f"matches the Ghia ground truth)")
96 sys.exit(0 if ok else 1)
97
98
99if __name__ == "__main__":
100 main()
run(N=128, Re=100.0, U=1.0, nz=4, dt=1.0, max_steps=6000, implicit=False, vmg=False, vlevels=8, vcycles=4, vel_iter=60, outer=2)