flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_colocated_open_boundary.py
Go to the documentation of this file.
1#!/usr/bin/env python
2"""Gate: the COLLOCATED solver stays stable and mass-conserving at an OPEN boundary.
3
4Why this test exists. Nothing in the registered suite exercised the collocated open boundary:
5every collocated ctest is either Stokes (no advecting velocity at all) or periodic/walled. So when
6the collocated advecting velocity became the projected face field (`doc/uf_advection.md`), the
7first version fed the last cell's upwind flux the one face `bcCorrectOutflow` sets from GLOBAL
8continuity rather than from the fluid beside it -- a positive feedback loop that took the
9developing channel to max|u| = 4.5e+02 by step 50 and NaN by step 69 -- and all 161 ctests passed.
10`scripts/verify_colocated_channel.py` and `scripts/verify_colocated_bfs.py` caught it, but they
11are ~20-minute full-fidelity studies. This is the cheap version of the same signal: one coarse
12channel, one resolution, long enough to develop.
13
14Checks, in the order they would fail:
15 1. the field is finite (the failure above was a blow-up, not a bias);
16 2. mass in == mass out (the outflow face is the mass-balance closure -- if advection corrupts it,
17 this is what goes);
18 3. the developed profile is Poiseuille, u_max/U_mean -> 1.5;
19 4. the projected face field is still discretely divergence-free.
20
21Run: OMP_NUM_THREADS=4 OMP_PROC_BIND=false PYTHONPATH=<build> \
22 python tests/python/test_colocated_open_boundary.py
23Exit 0 pass, 1 fail, 77 skipped (no module).
24"""
25import sys
26
27import numpy as np
28
29H, L, NZ = 16, 112, 4
30U, RE, DT, STEPS = 1.0, 100.0, 0.5, 400
31
32
33def build():
34 import peclet.flow as pf
35
36 s = pf.SolverColocated(L, H, NZ)
37 s.set_rho(1.0)
38 s.set_mu(U * H / RE)
39 s.set_dt(DT)
40 s.set_advection(True)
41 s.set_domain_bc("-x", "inflow", (U, 0.0, 0.0))
42 s.set_domain_bc("+x", "outflow")
43 s.set_domain_bc("-y", "wall")
44 s.set_domain_bc("+y", "wall")
45 s.diagnostics.set_velocity_solver_params(60)
46 s.set_pressure_pcg(True, 400, 1e-9)
47 s.set_pressure_geometry(np.asfortranarray(np.full((L, H, NZ), 1e30)))
48 return s
49
50
52 """Pytest entry point (tests/python is collected by ctest as pytest too)."""
53 s = build()
54 for _ in range(STEPS):
55 s.step()
56 u = np.asarray(s.get_u())
57 assert np.isfinite(u).all(), "collocated open boundary went non-finite"
58
59 k = NZ // 2
60 flux_in = float(u[2, :, k].sum())
61 flux_out = float(u[L - 3, :, k].sum())
62 mass_err = abs(flux_out - flux_in) / abs(flux_in)
63 assert mass_err < 1e-3, f"mass not conserved: {mass_err:.2e}"
64
65 prof = u[L - 4, :, k]
66 ratio = float(prof.max() / prof.mean())
67 assert 1.45 < ratio < 1.55, f"profile not developed Poiseuille: u_max/U_mean = {ratio:.4f}"
68
69 div = float(s.max_open_divergence())
70 assert div < 1e-3, f"face field not divergence-free: {div:.2e}"
71 return mass_err, ratio, div
72
73
74def main():
75 try:
76 import peclet.flow # noqa: F401
77 except ImportError:
78 print("SKIP: peclet.flow not importable (set PYTHONPATH to the build tree)")
79 return 77
80 try:
81 mass_err, ratio, div = test_colocated_open_boundary()
82 except AssertionError as e:
83 print(f"FAIL: {e}")
84 return 1
85 print(f"OK: collocated channel after {STEPS} steps -- mass_err {mass_err:.2e}, "
86 f"u_max/U_mean {ratio:.4f}, max_open_divergence {div:.2e}")
87 return 0
88
89
90if __name__ == "__main__":
91 sys.exit(main())