flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
verify_velocity_mg_upwind_sdflow.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Validation of the upwind-convective velocity-multigrid coarse operator (task #56, Phase 4 of
3doc/velocity_mg_plan.md). When implicit-FOU advection is on, the velocity (momentum) solve is an
4advection-dominated, non-symmetric Helmholtz system (I - nu*dt*Lap + dt*FOU(u^k)). The opt-in
5upwind-convective vel-MG builds the coarse operators as anisotropic const-coeff diffusion + a coarse
6first-order-upwind advection from the restricted advecting velocity, keeping every level an M-matrix.
7
8This checks, on a sphere in a periodic box (full 3-D NS + cut-cell IBM + cut-cell pressure, one GPU):
9 (1) the vel-MG path stays finite/bounded at high Re where the operator is advection-dominated;
10 (2) at steady state it matches the RB-GS implicit-FOU solve (same operator, MG just sets the rate)
11 -> drag-equivalent: U_max and U_mean agree to a tight tolerance.
12The fine residual + smoother guarantee the exact fine (sharp-IBM) answer regardless of the coarse op,
13so MG-on must converge to the same field as RB-GS.
14"""
15import os
16import sys
17
18import numpy as np
19
20sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..",
21 os.environ.get("SDFLOW_BUILD", "build_mpi"))))
22from peclet import flow as sdflow # noqa: E402
23
24N = 32
25NU = 0.1
26
27
28def sphere_sdf(rfrac=0.3): # sdf[x,y,z], negative inside the sphere
29 X, Y, Z = np.meshgrid(np.arange(N), np.arange(N), np.arange(N), indexing="ij")
30 return np.sqrt((X - N / 2.0) ** 2 + (Y - N / 2.0) ** 2 + (Z - N / 2.0) ** 2) - N * rfrac
31
32
33def run(vmg, dt, fx, n_steps, vlevels=3, vcycles=10, to_steady=False):
34 sdf = sphere_sdf()
35 s = sdflow.Solver(N, N, N)
36 s.set_rho(1.0)
37 s.set_mu(NU)
38 s.set_dt(dt)
39 s.set_body_force(fx, 0.0, 0.0)
40 s.set_advection(True)
41 s.set_implicit_advection(True)
42 s.set_outer_iterations(3)
43 s.set_velocity_solver_params(80) # RB-GS sweeps when vmg off
44 if vmg:
45 s.set_velocity_multigrid(True, vlevels, vcycles)
46 s.set_pressure_pcg(True, max_iter=120, rtol=1e-9)
47 s.set_solid(sdf, cutcell_pressure=True, pressure_coarse="galerkin")
48 prev = 0.0
49 for it in range(n_steps):
50 s.step()
51 u = s.get_u()
52 if not np.isfinite(u).all():
53 return None
54 um = float(u.mean())
55 if to_steady and it > 8 and abs(um - prev) < 1e-7 * (abs(um) + 1e-15):
56 break
57 prev = um
58 return s.get_u()
59
60
61def main():
62 print("=== upwind-convective velocity-MG: high-Re stability + RB-GS equivalence ===")
63
64 # (1) high Re / large dt: advection-dominated; the upwind coarse op must stay an M-matrix (no NaN)
65 print(" (1) high Re: dt=5, fx=0.02 (U~2 -> CFL >> 1, operator advection-dominated)")
66 ur = run(vmg=False, dt=5.0, fx=0.02, n_steps=30)
67 uv = run(vmg=True, dt=5.0, fx=0.02, n_steps=30)
68 rbgs_ok = ur is not None and np.isfinite(ur).all()
69 vmg_ok = uv is not None and np.isfinite(uv).all() and uv.max() < 1e3
70 print(f" RB-GS implicit-FOU : {'finite, U_max=%.3f' % ur.max() if rbgs_ok else 'unstable'}")
71 print(f" vel-MG implicit-FOU: {'finite, U_max=%.3f' % uv.max() if vmg_ok else 'unstable'}")
72
73 # (2) moderate Re, steady: MG-on must converge to the SAME field as RB-GS (fine residual is identical)
74 print(" (2) moderate Re: dt=5, fx=2e-4 (steady) -> RB-GS vs vel-MG agree")
75 ur2 = run(vmg=False, dt=5.0, fx=2e-4, n_steps=400, to_steady=True)
76 uv2 = run(vmg=True, dt=5.0, fx=2e-4, n_steps=400, to_steady=True)
77 rel_max = abs(uv2.max() - ur2.max()) / ur2.max()
78 rel_mean = abs(uv2.mean() - ur2.mean()) / abs(ur2.mean())
79 print(f" RB-GS U_max={ur2.max():.6f} U_mean={ur2.mean():.6e}")
80 print(f" vel-MG U_max={uv2.max():.6f} U_mean={uv2.mean():.6e}")
81 print(f" diff: U_max {rel_max*100:.3f}% U_mean {rel_mean*100:.3f}%")
82
83 ok = rbgs_ok and vmg_ok and rel_max < 0.01 and rel_mean < 0.01
84 print(f" result: {'PASS' if ok else 'FAIL'} (upwind vel-MG stable at high Re; "
85 f"matches RB-GS at steady to <1%)")
86 sys.exit(0 if ok else 1)
87
88
89if __name__ == "__main__":
90 main()
run(vmg, dt, fx, n_steps, vlevels=3, vcycles=10, to_steady=False)