flow 1.3.0
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
22from _bootstrap import ensure_flow # noqa: E402
23sdflow = ensure_flow()
24sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
25from verify_lid_cavity_sdflow import GHIA_Y, GHIA_U # noqa: E402
26
27
28def run(N=128, Re=100.0, U=1.0, nz=4, dt=1.0, max_steps=6000, implicit=False, vmg=False,
29 vlevels=8, vcycles=4, vel_iter=60, outer=2):
30 nu = U * N / Re
31 s = sdflow.Solver(N, N, nz)
32 s.set_rho(1.0); s.set_mu(nu); s.set_dt(dt); s.set_advection(True)
33 s.set_implicit_advection(implicit)
34 s.diagnostics.set_outer_iterations(outer)
35 s.set_domain_bc("-x", "wall"); s.set_domain_bc("+x", "wall"); s.set_domain_bc("-y", "wall")
36 s.set_domain_bc("+y", "inflow", (U, 0.0, 0.0)) # +y face = moving lid
37 s.diagnostics.set_velocity_solver_params(vel_iter)
38 if vmg:
39 s.set_velocity_multigrid(True, vlevels, vcycles)
40 s.set_pressure_multigrid(True, levels=8)
41 s.set_pressure_solver_params(80)
42 s.set_pressure_geometry(np.full((N, N, nz), 1e30))
43 prev = None
44 steps = max_steps
45 for it in range(max_steps):
46 s.step()
47 u = np.asarray(s.get_u()).reshape((N, N, nz), order="F")
48 if not np.isfinite(u).all() or np.abs(u).max() > 1e3:
49 return None # blew up
50 if it % 100 == 0:
51 cur = u[:, :, nz // 2].copy()
52 if prev is not None:
53 d = np.max(np.abs(cur - prev)) / (np.max(np.abs(cur)) + 1e-30)
54 if d < 2e-5:
55 steps = it + 1; break
56 prev = cur
57 u = np.asarray(s.get_u()).reshape((N, N, nz), order="F")
58 uc = 0.5 * (u[N // 2 - 1, :, nz // 2] + u[N // 2, :, nz // 2])
59 yc = (np.arange(N) + 0.5) / N
60 u_rms = float(np.sqrt(np.mean((np.interp(GHIA_Y, yc, uc) - GHIA_U) ** 2)))
61 return dict(u_rms=u_rms, umin=float(uc.min()), div=float(s.max_open_divergence()), steps=steps)
62
63
64def main():
65 N = int(os.environ.get("VMG_N", 64))
66 Re = 100.0
67 print(f"=== upwind velocity-MG on domain-BC (lid cavity Re={Re:.0f}, N={N}, quasi-2D nz=4) ===")
68
69 # (1) ground truth: explicit advection, small dt (stable) -> Ghia. Grid units: dx=1, lid U=1, so the
70 # advective CFL is just U*dt; dt=2 (CFL=2) is comfortably stable.
71 ref = run(N=N, Re=Re, dt=2.0, implicit=False, vmg=False, max_steps=12000)
72 print(f" (1) explicit dt=2 (CFL=2, stable): u_rms={ref['u_rms']:.4f} "
73 f"umin={ref['umin']:.4f} (Ghia -0.2058) div={ref['div']:.1e} steps={ref['steps']}")
74
75 # (2) high CFL: explicit blows up (threshold ~dt=20); implicit-FOU + upwind vmg stays stable + reaches
76 # the same steady state. Grid units dx=1 -> advective CFL = U*dt (here 40).
77 DT = float(os.environ.get("VMG_DT", 40.0))
78 print(f" (2) high CFL: dt={DT:.0f} (advective CFL = U*dt = {DT:.0f})")
79 ex = run(N=N, Re=Re, dt=DT, implicit=False, vmg=False, max_steps=4000)
80 iv = run(N=N, Re=Re, dt=DT, implicit=True, vmg=True, vlevels=8, vcycles=6, max_steps=4000)
81 expl_blew = ex is None
82 vmg_ok = iv is not None
83 print(f" explicit advection : {'BLEW UP' if expl_blew else 'u_rms=%.4f umin=%.4f' % (ex['u_rms'], ex['umin'])}")
84 if vmg_ok:
85 print(f" implicit-FOU + upwind vmg: u_rms={iv['u_rms']:.4f} umin={iv['umin']:.4f} "
86 f"div={iv['div']:.1e} steps={iv['steps']}")
87 else:
88 print(" implicit-FOU + upwind vmg: UNSTABLE")
89
90 # the implicit-FOU vmg solution must match the explicit ground-truth Ghia centreline
91 match = vmg_ok and abs(iv["u_rms"] - ref["u_rms"]) < 0.02 and iv["div"] < 1e-4
92 ok = expl_blew and vmg_ok and match
93 print(f" result: {'PASS' if ok else 'FAIL'} (upwind vmg stable at CFL>>1 where explicit fails; "
94 f"matches the Ghia ground truth)")
95 sys.exit(0 if ok else 1)
96
97
98if __name__ == "__main__":
99 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)