flow
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 os
15import sys
16import time
17
18import numpy as np
19
20sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", os.environ.get("SDFLOW_BUILD", "build"))))
21from peclet import flow as sdflow # noqa: E402
22
23
24def inlet_profile(H, S, nz, U_in):
25 prof = np.zeros((H, nz, 3))
26 yc = np.arange(H) + 0.5
27 eta = (yc - S) / S
28 up = yc > S
29 prof[up, :, 0] = (6.0 * U_in * eta * (1.0 - eta))[up, None]
30 return prof
31
32
33def reattachment(u_bottom):
34 reversed_yet = False
35 for i in range(1, len(u_bottom)):
36 if u_bottom[i] < 0.0:
37 reversed_yet = True
38 elif reversed_yet and u_bottom[i] >= 0.0:
39 return (i - 1) + u_bottom[i - 1] / (u_bottom[i - 1] - u_bottom[i])
40 return 0.0
41
42
43def run(Re, S=16, Lr=12, U_in=1.0, nz=4, dt=0.4, max_steps=8000):
44 H, L = 2 * S, Lr * S
45 nu = U_in * S / Re
46 s = sdflow.SolverColocated(L, H, nz)
47 s.set_rho(1.0); s.set_mu(nu); s.set_dt(dt); s.set_advection(True)
48 s.set_domain_bc_profile(0, inlet_profile(H, S, nz, U_in)) # -x partial parabolic inlet (-> inflow)
49 s.set_domain_bc(1, 3) # +x outflow
50 s.set_domain_bc(2, 1); s.set_domain_bc(3, 1) # -y, +y no-slip walls
51 s.set_velocity_solver_params(60)
52 s.set_pressure_pcg(True, 400, 1e-9)
53 s.set_pressure_geometry(np.asfortranarray(np.full((L, H, nz), 1e30)))
54
55 t0 = time.time()
56 prev = 0.0
57 steps = max_steps
58 for it in range(max_steps):
59 s.step()
60 if it % 100 == 99:
61 xr = reattachment(s.get_u()[:, 0, nz // 2])
62 if it > 3000 and xr > S and abs(xr - prev) < 1e-3 * S:
63 steps = it + 1
64 break
65 prev = xr
66 u = s.get_u()
67 xr = reattachment(u[:, 0, nz // 2])
68 flux_in = float(u[2, :, nz // 2].sum())
69 flux_out = float(u[L - 3, :, nz // 2].sum())
70 mass_err = abs(flux_out - flux_in) / (abs(flux_in) + 1e-30)
71 bubble = bool((u[1:S, 0, nz // 2] < 0).any())
72 return dict(Re=Re, S=S, L=L, steps=steps, secs=time.time() - t0, xr_S=xr / S,
73 div=float(s.max_open_divergence()), mass_err=mass_err, bubble=bubble)
74
75
76def main():
77 print("=== sdflow phase-5b: collocated backward-facing step (Gartling expansion ratio 2) ===")
78 laminar = [run(100, max_steps=5000), run(200, max_steps=8000)]
79 ok = True
80 xr_prev = -1.0
81 for r in laminar:
82 print(f" Re_S={r['Re']:<4d} S={r['S']} L={r['L']} x_r/S={r['xr_S']:.2f} bubble={r['bubble']} "
83 f"mass_err={r['mass_err']:.1e} div={r['div']:.1e} ({r['steps']} steps, {r['secs']:.0f}s)")
84 ok &= (r["bubble"] and r["mass_err"] < 1e-3 and r["div"] < 2e-3 and 0.5 < r["xr_S"] < 12.0
85 and r["xr_S"] > xr_prev)
86 xr_prev = r["xr_S"]
87 print(f" (staggered reference: x_r/S 5.3 -> 8.3 on the Armaly/Biswas curve)")
88 print(f" result: {'PASS' if ok else 'FAIL'} (recirculation grows with Re, mass-conserving)")
89 sys.exit(0 if ok else 1)
90
91
92if __name__ == "__main__":
93 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)