flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
verify_colocated_bfs.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Phase-5b verification (collocated grid): the backward-facing step (BFS) -- the separated-flow benchmark
3and the showcase for the collocated per-face INLET PROFILE. Gartling (1990) expansion-ratio-2 step: a
4channel of full height H=2S with no-slip walls top/bottom; flow enters ONLY the upper half (y in [S,2S])
5with the developed parabola and is zero over the lower half (the step face); it leaves through the outflow.
6The step is realized purely as the inlet condition (set_domain_bc_profile -> the collocated profile inlet
7ghost, bcVelocityColocated with a per-position value), no immersed solid.
8
9Laminar de-risk (Re_S = U_in*S/nu = 100, 200): a recirculation bubble must form behind the step, the
10reattachment length x_r/S must grow with Re, mass is conserved, and the flow stays incompressible (the open
11outflow leaves the approximate projection's O(h^2) residual, looser than the staggered 1e-6). The staggered
12solver reaches x_r/S 5.3 (Re=100) -> 8.3 (Re=200) on the Armaly/Biswas curve.
13"""
14import sys
15import time
16
17import numpy as np
18
19from _bootstrap import ensure_flow # noqa: E402
20sdflow = ensure_flow()
21
22
23def inlet_profile(H, S, nz, U_in):
24 prof = np.zeros((H, nz, 3))
25 yc = np.arange(H) + 0.5
26 eta = (yc - S) / S
27 up = yc > S
28 prof[up, :, 0] = (6.0 * U_in * eta * (1.0 - eta))[up, None]
29 return prof
30
31
32def reattachment(u_bottom):
33 reversed_yet = False
34 for i in range(1, len(u_bottom)):
35 if u_bottom[i] < 0.0:
36 reversed_yet = True
37 elif reversed_yet and u_bottom[i] >= 0.0:
38 return (i - 1) + u_bottom[i - 1] / (u_bottom[i - 1] - u_bottom[i])
39 return 0.0
40
41
42def run(Re, S=16, Lr=12, U_in=1.0, nz=4, dt=0.4, max_steps=8000):
43 H, L = 2 * S, Lr * S
44 nu = U_in * S / Re
45 s = sdflow.SolverColocated(L, H, nz)
46 s.set_rho(1.0); s.set_mu(nu); s.set_dt(dt); s.set_advection(True)
47 s.set_domain_bc_profile("-x", inlet_profile(H, S, nz, U_in)) # -x partial parabolic inlet (-> inflow)
48 s.set_domain_bc("+x", "outflow") # +x outflow
49 s.set_domain_bc("-y", "wall"); s.set_domain_bc("+y", "wall") # -y, +y no-slip walls
50 s.diagnostics.set_velocity_solver_params(60)
51 s.set_pressure_pcg(True, 400, 1e-9)
52 s.set_pressure_geometry(np.asfortranarray(np.full((L, H, nz), 1e30)))
53
54 t0 = time.time()
55 prev = 0.0
56 steps = max_steps
57 for it in range(max_steps):
58 s.step()
59 if it % 100 == 99:
60 xr = reattachment(s.get_u()[:, 0, nz // 2])
61 if it > 3000 and xr > S and abs(xr - prev) < 1e-3 * S:
62 steps = it + 1
63 break
64 prev = xr
65 u = s.get_u()
66 xr = reattachment(u[:, 0, nz // 2])
67 flux_in = float(u[2, :, nz // 2].sum())
68 flux_out = float(u[L - 3, :, nz // 2].sum())
69 mass_err = abs(flux_out - flux_in) / (abs(flux_in) + 1e-30)
70 bubble = bool((u[1:S, 0, nz // 2] < 0).any())
71 return dict(Re=Re, S=S, L=L, steps=steps, secs=time.time() - t0, xr_S=xr / S,
72 div=float(s.max_open_divergence()), mass_err=mass_err, bubble=bubble)
73
74
75def main():
76 print("=== sdflow phase-5b: collocated backward-facing step (Gartling expansion ratio 2) ===")
77 laminar = [run(100, max_steps=5000), run(200, max_steps=8000)]
78 ok = True
79 xr_prev = -1.0
80 for r in laminar:
81 print(f" Re_S={r['Re']:<4d} S={r['S']} L={r['L']} x_r/S={r['xr_S']:.2f} bubble={r['bubble']} "
82 f"mass_err={r['mass_err']:.1e} div={r['div']:.1e} ({r['steps']} steps, {r['secs']:.0f}s)")
83 ok &= (r["bubble"] and r["mass_err"] < 1e-3 and r["div"] < 2e-3 and 0.5 < r["xr_S"] < 12.0
84 and r["xr_S"] > xr_prev)
85 xr_prev = r["xr_S"]
86 print(f" (staggered reference: x_r/S 5.3 -> 8.3 on the Armaly/Biswas curve)")
87 print(f" result: {'PASS' if ok else 'FAIL'} (recirculation grows with Re, mass-conserving)")
88 sys.exit(0 if ok else 1)
89
90
91if __name__ == "__main__":
92 main()
inlet_profile(H, S, nz, U_in)
run(Re, S=16, Lr=12, U_in=1.0, nz=4, dt=0.4, max_steps=8000)