flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
verify_channel_sdflow.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Verification (sdflow): developing plane channel flow -- the canonical INFLOW/OUTFLOW benchmark. A
3uniform stream U enters at -x (inflow, Dirichlet velocity), leaves at +x (outflow: zero-gradient velocity
4+ Dirichlet p=0), between no-slip walls at +-y; quasi-2D (periodic z). This exercises the open-boundary
5machinery the cavity cannot: the operator/flux openness split (inflow Neumann pressure but its flux is
6counted; outflow Dirichlet pressure), the zero-gradient outflow velocity, and the outflow projection
7correction that lets mass leave.
8
9A uniform inlet develops into the parabolic Poiseuille profile over the entrance length
10L_e ~ 0.04*Re*H. With L >~ 6H the outlet is fully developed, so we check:
11 * global mass conservation: flux(inlet) == flux(outlet) (continuity, exact)
12 * incompressibility: max cut-cell flux divergence -> 0
13 * developed profile: u(y) at the outlet is parabolic, u_max/U_mean -> 1.5
14 * (informational) developed pressure gradient ~ -12*mu*U_mean/H^2.
15
16Uses the canonical `sdflow` module. NO immersed solid. Physical units: set_rho/set_mu; Re = U*H/nu.
17"""
18import os
19import sys
20
21import numpy as np
22
23sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..",
24 os.environ.get("SDFLOW_BUILD", "build_mpi"))))
25from peclet import flow as sdflow # noqa: E402
26
27
28def run(H=32, L=224, Re=100.0, U=1.0, nz=4, max_steps=8000, dt=0.5):
29 """Run the developing-channel case and return its convergence diagnostics.
30
31 Uniform inflow at -x, outflow at +x, no-slip walls at +-y, periodic z; marches to steady state and
32 returns a dict with the mass-conservation error, max divergence, outlet u_max/U_mean ratio, the
33 outlet-profile rms against the parabola, and the developed pressure gradient. Returns None off root.
34 """
35 nu = U * H / Re
36 s = sdflow.Solver(L, H, nz)
37 s.set_rho(1.0); s.set_mu(nu); s.set_dt(dt); s.set_advection(True)
38 s.set_domain_bc(0, 2, U, 0.0, 0.0) # -x inflow: uniform stream
39 s.set_domain_bc(1, 3) # +x outflow
40 s.set_domain_bc(2, 1); s.set_domain_bc(3, 1) # -y, +y no-slip walls
41 s.set_velocity_solver_params(60)
42 s.set_pressure_multigrid(True, levels=8) # semi-coarsening MG (z frozen, x/y deep; auto-capped)
43 s.set_pressure_solver_params(80)
44 s.set_pressure_geometry(np.full((L, H, nz), 1e30)) # all-fluid + BC pressure faces
45
46 prev = 0.0
47 steps = max_steps
48 for it in range(max_steps):
49 s.step()
50 if it % 50 == 49:
51 u = s.get_u()
52 m = float(u[L - 4, H // 2, nz // 2]) if s.rank() == 0 else 0.0 # outlet centreline u
53 done = it > 1000 and abs(m - prev) < 1e-5 * (abs(m) + 1e-30)
54 prev = m
55 if s.bcast_from_root(done):
56 steps = it + 1
57 break
58 u = s.get_u(); p = s.get_p(); div = s.max_open_divergence()
59 if s.rank() != 0:
60 return None
61 # mass conservation: streamwise flux at an inlet station vs an outlet station (continuity)
62 flux_in = float(u[2, :, nz // 2].sum())
63 flux_out = float(u[L - 3, :, nz // 2].sum())
64 mass_err = abs(flux_out - flux_in) / (abs(flux_in) + 1e-30)
65 # developed outlet profile vs the plane-Poiseuille parabola u = 6*U_mean*eta*(1-eta), eta=(j+.5)/H
66 prof = u[L - 4, :, nz // 2]
67 U_mean = float(prof.mean())
68 eta = (np.arange(H) + 0.5) / H
69 parab = 6.0 * U_mean * eta * (1.0 - eta)
70 prof_rms = float(np.sqrt(np.mean((prof - parab) ** 2)) / (abs(U_mean) + 1e-30))
71 ratio = float(prof.max() / (U_mean + 1e-30))
72 # informational: developed-region streamwise pressure gradient vs -12*mu*U_mean/H^2
73 pc = p[:, H // 2, nz // 2]
74 x0, x1 = L // 2, L - 6
75 dpdx = float((pc[x1] - pc[x0]) / (x1 - x0))
76 dpdx_analytic = -12.0 * nu * U_mean / H**2
77 return dict(steps=steps, mass_err=mass_err, div=div, U_mean=U_mean, ratio=ratio,
78 prof_rms=prof_rms, dpdx=dpdx, dpdx_analytic=dpdx_analytic, Re=Re, H=H, L=L)
79
80
81def main():
82 """Run the default channel case, print the diagnostics, and exit non-zero if it fails the tolerance."""
83 r = run()
84 if r is None:
85 return
86 print("=== sdflow: developing plane channel (inflow/outflow) ===")
87 print(f" Re={r['Re']:.0f} H={r['H']} L={r['L']} ({r['steps']} steps)")
88 print(f" mass conservation: |flux_out - flux_in|/flux_in = {r['mass_err']:.2e}")
89 print(f" max flux divergence = {r['div']:.1e}")
90 print(f" outlet u_max/U_mean = {r['ratio']:.4f} (Poiseuille 1.5) profile rms = {r['prof_rms']:.4f}")
91 print(f" developed dp/dx = {r['dpdx']:.2e} (analytic -12 mu U/H^2 = {r['dpdx_analytic']:.2e})")
92 ok = (r["mass_err"] < 1e-3 and r["div"] < 1e-6 and 1.45 < r["ratio"] < 1.55 and r["prof_rms"] < 0.03)
93 print(f" result: {'PASS' if ok else 'FAIL'} (mass-conserving, divergence-free, developed parabola)")
94 sys.exit(0 if ok else 1)
95
96
97if __name__ == "__main__":
98 main()
run(H=32, L=224, Re=100.0, U=1.0, nz=4, max_steps=8000, dt=0.5)