flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_balanced_force_restart.py
Go to the documentation of this file.
1#!/usr/bin/env python
2"""Gate: the balanced-force projection's pressure split P = P_dyn + P_b never counts the balanced
3pressure twice (doc/collocated_varrho_forces.md ยง4.6.2, the re-split rule).
4
5The option keeps P the total pressure and P_b ("p_balanced") its balanced part; a continuing step
6does P += X - P_b, P_b = X. When P_b is NOT the split of the current P -- steps with the option off
7moved P, or a restart restored "p" but not "p_balanced" -- that update adds the balanced pressure a
8second time (measured before the fix on this column: a restart gave max|u| 1.25e-2 against 7.8e-14
9continued; OFF 400 steps then ON gave 1.27e-2). The rule: such a step RE-SPLITS (P_b = X, P
10unchanged), and restoring "p_balanced" together with "p" keeps the split.
11
12Configuration: a walled ratio-1000 column (8 x 8 x 24, heavy lower half, walls at -z/+z, mu 0.1,
13dt 1, g 0.1, Stokes, Chebyshev rtol 1e-12), on SolverColocated (V8: the option is its default) and
14on Solver (option explicitly on). Scenarios, each against its twin:
15 continued 30 steps, then one more (the reference level)
16 restart u, v, w, p restored into a fresh solver, one step (re-split)
17 restart + P_b u, v, w, p and p_balanced restored, one step (keeps the split)
18 OFF -> ON 400 steps OFF, then ON for 1 and 6 steps, against the OFF run continued
19Gates: every scenario's velocity differs from its twin by < 1e-12 (max over u, v, w), and the two
20restarts stay below 1e-12 absolute. On a static force a re-split step IS the OFF step, so the
21OFF -> ON run tracks the OFF run to round-off; its absolute level is the OFF transient's (1.8e-4
22cell velocity on V8 at step 400, exact on the staggered 1-D column), not the continued ON run's.
23
24Run: OMP_NUM_THREADS=4 OMP_PROC_BIND=false PYTHONPATH=<build> python tests/python/test_balanced_force_restart.py
25Exit 0 pass, 1 fail, 77 skipped (no module).
26"""
27import sys
28
29import numpy as np
30
31try:
32 import peclet.flow as pf
33except ImportError:
34 print("SKIP: peclet.flow not importable")
35 sys.exit(77)
36
37N, NZ, G = 8, 24, 0.1
38TOL = 1e-12
39
40
41def F(a):
42 return np.asfortranarray(np.asarray(a, dtype=np.float64))
43
44
45RHO = np.where(np.arange(NZ) < NZ // 2, 1000.0, 1.0)[None, None, :] * np.ones((N, N, NZ))
46
47
48def build(grid, bfp):
49 s = (pf.SolverColocated if grid == "colo" else pf.Solver)(N, N, NZ)
50 s.set_rho(1.0)
51 s.set_mu(0.1)
52 s.set_dt(1.0)
53 s.set_advection(False)
54 s.set_domain_bc("-z", "wall")
55 s.set_domain_bc("+z", "wall")
56 if grid == "colo":
57 s.set_collocated_scheme("gauge-exact")
58 s.set_pressure_geometry(F(np.full((N, N, NZ), 10.0)))
59 s.enable_cell_force()
60 for nm, a in (("force_x", 0 * RHO), ("force_y", 0 * RHO), ("force_z", -RHO * G)):
61 s.set_field(nm, F(a))
62 s.diagnostics.exchange_field(nm)
63 s.add_field("rho")
64 s.set_field("rho", F(RHO))
65 s.diagnostics.exchange_field("rho")
66 s.diagnostics.set_density_mode("variable")
67 if bfp is not None:
68 s.set_balanced_force_projection(bfp)
69 s.set_pressure_chebyshev(True, 2000, 1e-12)
70 return s
71
72
73def vel(s):
74 return np.stack([np.asarray(s.get_u()), np.asarray(s.get_v()), np.asarray(s.get_w())])
75
76
77def check(label, u, ref, absolute):
78 d = float(np.max(np.abs(u - ref)))
79 a = float(np.max(np.abs(u)))
80 ok = np.isfinite(d) and d < TOL and (not absolute or a < TOL)
81 print(f" {label:<28s} max|u| {a:.3e} |u - twin| {d:.3e} {'ok' if ok else 'FAIL'}")
82 return ok
83
84
85def run(grid):
86 on = None if grid == "colo" else True # V8: the option is the default
87 print(f"--- {'SolverColocated (V8, default ON)' if grid == 'colo' else 'Solver (ON)'}")
88 ok = True
89 a = build(grid, on)
90 if not a.balanced_force_projection:
91 print(" option not active FAIL")
92 return False
93 if "p_balanced" not in a.field_names(): # registered as soon as the option is active
94 print(" p_balanced not registered before the first step FAIL")
95 return False
96 for _ in range(30):
97 a.step()
98 st = {n: np.asarray(a.get_field(n)).copy() for n in ("u", "v", "w", "p", "p_balanced")}
99 a.step()
100 ua = vel(a)
101 ok &= check("continued", ua, ua, True)
102 for keep_pb in (False, True):
103 b = build(grid, on)
104 for n, v in st.items():
105 if n == "p_balanced" and not keep_pb:
106 continue
107 b.set_field(n, F(v))
108 b.diagnostics.exchange_field(n)
109 b.step()
110 ok &= check("restart + p_balanced" if keep_pb else "restart (u, v, w, p)", vel(b), ua,
111 True)
112 c, d = build(grid, False), build(grid, False)
113 for _ in range(400):
114 c.step()
115 d.step()
116 c.set_balanced_force_projection(True)
117 for k in range(1, 7):
118 c.step()
119 d.step()
120 if k in (1, 6):
121 ok &= check(f"OFF 400 -> ON, step {k}", vel(c), vel(d), False)
122 return ok
123
124
125def main():
126 ok = run("colo") & run("stag")
127 print("PASS" if ok else "FAIL")
128 return 0 if ok else 1
129
130
132 assert main() == 0
133
134
135if __name__ == "__main__":
136 sys.exit(main())
static constexpr double F