flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
verify_colocated_channel.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Phase-5b verification (collocated grid): developing plane channel -- the canonical INFLOW/OUTFLOW
3benchmark. A uniform stream enters at -x (inflow Dirichlet), leaves at +x (outflow: zero-gradient velocity
4+ Dirichlet p=0), between no-slip walls at +-y; quasi-2D (periodic z). Exercises the collocated open-boundary
5machinery: the inflow reflection ghost (carrying the prescribed normal velocity through the open face),
6the zero-gradient outflow velocity ghost, and the outflow face correction on the MAC field that lets mass
7leave (the shared operator/flux openness split is grid-agnostic).
8
9Develops into the parabolic Poiseuille profile (u_max/U_mean -> 1.5). We check global mass conservation,
10the developed profile, and incompressibility. NOTE on the divergence: the *face* field is divergence-free
11to machine precision in the interior; at the open outflow the approximate projection leaves a small O(h^2)
12residual (vs the staggered exact projection), so the divergence tolerance here is looser than the staggered
131e-6 and is checked to SHRINK with resolution. Global mass conservation (flux_in == flux_out) is the
14primary continuity check.
15"""
16import os
17import sys
18
19import numpy as np
20
21sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", os.environ.get("SDFLOW_BUILD", "build"))))
22from peclet import flow as sdflow # noqa: E402
23
24
25def run(H, L, Re=100.0, U=1.0, nz=4, max_steps=8000, dt=0.5):
26 nu = U * H / Re
27 s = sdflow.SolverColocated(L, H, nz)
28 s.set_rho(1.0); s.set_mu(nu); s.set_dt(dt); s.set_advection(True)
29 s.set_domain_bc(0, 2, U, 0.0, 0.0) # -x inflow: uniform stream
30 s.set_domain_bc(1, 3) # +x outflow
31 s.set_domain_bc(2, 1); s.set_domain_bc(3, 1) # -y, +y no-slip walls
32 s.set_velocity_solver_params(60)
33 s.set_pressure_pcg(True, 400, 1e-9)
34 s.set_pressure_geometry(np.asfortranarray(np.full((L, H, nz), 1e30)))
35
36 prev = 0.0
37 steps = max_steps
38 for it in range(max_steps):
39 s.step()
40 if it % 50 == 49:
41 m = float(s.get_u()[L - 4, H // 2, nz // 2])
42 if it > 1000 and abs(m - prev) < 1e-5 * (abs(m) + 1e-30):
43 steps = it + 1
44 break
45 prev = m
46 u = s.get_u(); div = float(s.max_open_divergence())
47 flux_in = float(u[2, :, nz // 2].sum())
48 flux_out = float(u[L - 3, :, nz // 2].sum())
49 mass_err = abs(flux_out - flux_in) / (abs(flux_in) + 1e-30)
50 prof = u[L - 4, :, nz // 2]
51 U_mean = float(prof.mean())
52 eta = (np.arange(H) + 0.5) / H
53 parab = 6.0 * U_mean * eta * (1.0 - eta)
54 prof_rms = float(np.sqrt(np.mean((prof - parab) ** 2)) / (abs(U_mean) + 1e-30))
55 ratio = float(prof.max() / (U_mean + 1e-30))
56 return dict(steps=steps, mass_err=mass_err, div=div, ratio=ratio, prof_rms=prof_rms, H=H, L=L)
57
58
59def main():
60 print("=== sdflow phase-5b: collocated developing channel (inflow/outflow) ===")
61 print(f"{'H':>4} {'L':>5} {'steps':>6} {'mass_err':>10} {'maxdiv':>10} {'u_max/Um':>9} {'prof_rms':>9}")
62 rows = []
63 for H, L in ((16, 112), (32, 224)):
64 r = run(H, L)
65 print(f"{r['H']:4d} {r['L']:5d} {r['steps']:6d} {r['mass_err']:10.2e} {r['div']:10.2e} {r['ratio']:9.4f} {r['prof_rms']:9.4f}")
66 rows.append(r)
67 fine = rows[-1]
68 mass_ok = fine["mass_err"] < 1e-3
69 div_shrinks = rows[1]["div"] < rows[0]["div"]
70 div_small = fine["div"] < 1e-4
71 developed = 1.45 < fine["ratio"] < 1.55 and fine["prof_rms"] < 0.03
72 ok = mass_ok and div_small and div_shrinks and developed
73 print(f" mass conservation (flux_out==flux_in, <1e-3): {mass_ok} ({fine['mass_err']:.2e})")
74 print(f" outflow divergence small (<1e-4) and shrinks with N: {div_small and div_shrinks} "
75 f"({rows[0]['div']:.1e} -> {rows[1]['div']:.1e})")
76 print(f" developed Poiseuille (u_max/U_mean~1.5, rms<0.03): {developed} "
77 f"({fine['ratio']:.4f}, {fine['prof_rms']:.4f})")
78 print(f" result: {'PASS' if ok else 'FAIL'}")
79 sys.exit(0 if ok else 1)
80
81
82if __name__ == "__main__":
83 main()
run(H, L, Re=100.0, U=1.0, nz=4, max_steps=8000, dt=0.5)