flow 0.4.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
verify_poiseuille_ghostproj.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Verification (flow, ghost projection): plane Poiseuille through an SDF channel with the
3EXPERIMENTAL directional ghost-cell projection active (set_ghost_projection + cutcell_pressure).
4
5The steady parabola u(y) = F/(2 mu)(y-ylo)(yhi-y) is exactly quadratic, so a second-order scheme
6must reproduce it pointwise to solver tolerance. For this unidirectional flow the projection RHS
7is identically zero (u* depends on y only, v = w = 0, and every ghost closure sees zero data), so
8this gates that the ghost-projection machinery — overlay build, binary surrogate, BiCGStab — does
9NOT corrupt an exact solution (classification/indexing bugs inject spurious divergence and break
10the parabola). The closure-sign/theta physics is gated by tests/study/staggered_zh_ghostproj.py.
11
12Single-rank only (ghost projection v1). Pattern: scripts/verify_poiseuille_flow.py.
13"""
14import os
15import sys
16
17import numpy as np
18
19sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..",
20 os.environ.get("SDFLOW_BUILD", "build_cuda2"))))
21from peclet import flow # noqa: E402
22
23
24def channel_sdf(nx, ny, nz, ylo, yhi):
25 gy = np.arange(ny, dtype=np.float64)
26 sdf = np.empty((nx, ny, nz))
27 sdf[:, :, :] = np.minimum(gy - ylo, yhi - gy)[None, :, None]
28 return sdf
29
30
31def run(N, ghost, rho=1.0, mu=0.1, dt=50.0, F=0.01, max_steps=400):
32 nx, nz = 8, 8
33 ny = N
34 ylo = round(0.30 * ny) + 0.5 # non-integer walls -> cut cells
35 yhi = round(0.70 * ny) + 0.5
36
37 s = flow.Solver(nx, ny, nz)
38 s.set_rho(rho)
39 s.set_mu(mu)
40 s.set_dt(dt)
41 s.set_body_force(F, 0.0, 0.0)
42 s.set_velocity_solver_params(200)
43 s.set_pressure_pcg(True, 200, 1e-8)
44 if ghost:
45 s.set_ghost_projection(True)
46 s.set_solid(channel_sdf(nx, ny, nz, ylo, yhi), cutcell_pressure=True)
47
48 prev = 0.0
49 for it in range(max_steps):
50 s.step()
51 u_now = float(s.get_u().max())
52 if it > 5 and abs(u_now - prev) < 1e-10 * (abs(u_now) + 1e-12):
53 break
54 prev = u_now
55
56 u = s.get_u()
57 prof = u[nx // 2, :, nz // 2]
58 gy = np.arange(ny, dtype=np.float64)
59 fluid = (gy > ylo) & (gy < yhi)
60 u_ana = (F / (2.0 * mu)) * (gy - ylo) * (yhi - gy)
61 err = float(np.max(np.abs(prof[fluid] - u_ana[fluid])))
62 return err, s.max_open_divergence()
63
64
65def main():
66 print("=== flow: Poiseuille + GHOST projection -- pointwise node error vs the parabola ===")
67 print(f"{'mode':>9} {'Ny':>5} {'max|u - u_ana|':>16} {'max div':>12}")
68 worst = 0.0
69 for name, ghost in (("cutcell", False), ("ghost", True)):
70 for N in (16, 32, 64):
71 err, dv = run(N, ghost)
72 print(f"{name:>9} {N:5d} {err:16.3e} {dv:12.3e}")
73 if ghost:
74 worst = max(worst, err)
75 ok = worst < 1e-4
76 print(f" ghost-mode worst node error = {worst:.3e} (exact-on-quadratic -> solver tol)")
77 print(f" result: {'PASS' if ok else 'FAIL'}")
78 sys.exit(0 if ok else 1)
79
80
81if __name__ == "__main__":
82 main()
run(N, ghost, rho=1.0, mu=0.1, dt=50.0, F=0.01, max_steps=400)