flow 1.3.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 sys
15
16import numpy as np
17
18from _bootstrap import ensure_flow # noqa: E402
19flow = ensure_flow()
20
21
22def channel_sdf(nx, ny, nz, ylo, yhi):
23 gy = np.arange(ny, dtype=np.float64)
24 sdf = np.empty((nx, ny, nz))
25 sdf[:, :, :] = np.minimum(gy - ylo, yhi - gy)[None, :, None]
26 return sdf
27
28
29def run(N, ghost, rho=1.0, mu=0.1, dt=50.0, F=0.01, max_steps=400):
30 nx, nz = 8, 8
31 ny = N
32 ylo = round(0.30 * ny) + 0.5 # non-integer walls -> cut cells
33 yhi = round(0.70 * ny) + 0.5
34
35 s = flow.Solver(nx, ny, nz)
36 s.set_rho(rho)
37 s.set_mu(mu)
38 s.set_dt(dt)
39 s.set_body_force((F, 0.0, 0.0))
40 s.diagnostics.set_velocity_solver_params(200)
41 s.set_pressure_pcg(True, 200, 1e-8)
42 if ghost:
43 s.diagnostics.set_ghost_projection(True)
44 s.set_solid(channel_sdf(nx, ny, nz, ylo, yhi), cutcell_pressure=True)
45
46 prev = 0.0
47 for it in range(max_steps):
48 s.step()
49 u_now = float(s.get_u().max())
50 if it > 5 and abs(u_now - prev) < 1e-10 * (abs(u_now) + 1e-12):
51 break
52 prev = u_now
53
54 u = s.get_u()
55 prof = u[nx // 2, :, nz // 2]
56 gy = np.arange(ny, dtype=np.float64)
57 fluid = (gy > ylo) & (gy < yhi)
58 u_ana = (F / (2.0 * mu)) * (gy - ylo) * (yhi - gy)
59 err = float(np.max(np.abs(prof[fluid] - u_ana[fluid])))
60 return err, s.max_open_divergence()
61
62
63def main():
64 print("=== flow: Poiseuille + GHOST projection -- pointwise node error vs the parabola ===")
65 print(f"{'mode':>9} {'Ny':>5} {'max|u - u_ana|':>16} {'max div':>12}")
66 worst = 0.0
67 for name, ghost in (("cutcell", False), ("ghost", True)):
68 for N in (16, 32, 64):
69 err, dv = run(N, ghost)
70 print(f"{name:>9} {N:5d} {err:16.3e} {dv:12.3e}")
71 if ghost:
72 worst = max(worst, err)
73 ok = worst < 1e-4
74 print(f" ghost-mode worst node error = {worst:.3e} (exact-on-quadratic -> solver tol)")
75 print(f" result: {'PASS' if ok else 'FAIL'}")
76 sys.exit(0 if ok else 1)
77
78
79if __name__ == "__main__":
80 main()
run(N, ghost, rho=1.0, mu=0.1, dt=50.0, F=0.01, max_steps=400)