flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
staggered_zh_ghostproj_modes.py
Go to the documentation of this file.
1"""Ghost-projection closure-order comparison on staggered Zick & Homsy drag:
2 (2,2) full quadratic — 13-point nonsymmetric matrix (baseline ghost mode, cached below)
3 (1,1) linear closure — 7-point matrix, 1st-order closure everywhere
4 (1,2) MIXED/deferred — quadratic RHS (2nd-order steady constraint) on the 7-point linear
5 matrix; the operator mismatch converges through the time stepping (rate ~0.4 measured
6 a-priori). Expectation: (1,2) matches (2,2)'s accuracy with fewer BiCGStab iterations;
7 (1,1) shows whether the linear closure's larger O(h^2) constant (or 1st-order term)
8 degrades the drag. Cached columns from tests/study/staggered_zh_ghostproj.py runs."""
9import os
10import sys
11import time
12
13sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "scripts"))
14from _bootstrap import ensure_flow # noqa: E402
15flow = ensure_flow()
16
17import numpy as np
18
19def lattice_sdf(N, phi=0.125):
20 R = (3 * phi / (4 * np.pi)) ** (1 / 3) * N
21 g = np.arange(N) + 0.5
22 X, Y, Z = np.meshgrid(g, g, g, indexing="ij")
23 dx = X - 0.5 * N
24 dx -= N * np.round(dx / N)
25 dy = Y - 0.5 * N
26 dy -= N * np.round(dy / N)
27 dz = Z - 0.5 * N
28 dz -= N * np.round(dz / N)
29 return np.sqrt(dx * dx + dy * dy + dz * dz) - R, R
30
31
32def drag(N, morder, rorder, mu=0.1, F=1e-3, dt=80.0, warm_tol=1e-7, tail=40, max_steps=4000):
33 sdf, R = lattice_sdf(N)
34 lv = max(2, int(np.log2(N)) - 1)
35 s = flow.Solver(N, N, N)
36 s.set_rho(1.0)
37 s.set_mu(mu)
38 s.set_dt(dt)
39 s.set_body_force((F, 0, 0))
40 s.set_advection(False)
41 s.diagnostics.set_velocity_solver_params(200)
42 s.set_pressure_multigrid(True, levels=lv)
43 s.set_pressure_pcg(True, 400, 1e-10)
44 s.diagnostics.set_ghost_projection(True, matrix_order=morder, rhs_order=rorder)
45 s.set_solid(sdf, cutcell_pressure=True)
46 prev, warm, um, t0 = 0.0, None, [], time.time()
47 for it in range(max_steps):
48 s.step()
49 m = float(s.get_u().mean())
50 um.append(m)
51 if warm is None:
52 if it % 10 == 9:
53 if it > 10 and abs(m - prev) < warm_tol * (abs(m) + 1e-30):
54 warm = it
55 prev = m
56 elif it - warm >= tail:
57 break
58 K = F * N**3 / (6 * np.pi * mu * R * np.mean(um[-tail:]))
59 return K, it + 1, s.diagnostics.last_pressure_iterations(), s.max_open_divergence(), time.time() - t0
60
61
62kref = 4.2920
63# cached from staggered_zh_ghostproj.py (2026-07-12, build_cuda2): cutcell PCG + ghost (2,2)
64CUT = {32: -0.314, 48: -0.082, 64: -0.018, 96: +0.009, 128: +0.013}
65G22 = {32: (-0.609, 11), 48: (-0.256, 13), 64: (-0.144, 13), 96: (-0.066, 16), 128: (-0.039, 15)}
66
67print(f"Z&H K={kref}. Ghost closure orders: (2,2) quad [cached], (1,1) linear, (1,2) mixed.",
68 flush=True)
69print(f"{'N':>4} | {'cutcell%':>9} | {'(2,2)%':>8} {'it':>3} | {'(1,1)%':>8} {'ord':>5} {'it':>3} "
70 f"{'div':>8} | {'(1,2)%':>8} {'ord':>5} {'it':>3} {'div':>8} | secs", flush=True)
71prev = {}
72for N in (32, 48, 64, 96, 128):
73 K11, s11, i11, d11, t11 = drag(N, 1, 1)
74 K12, s12, i12, d12, t12 = drag(N, 1, 2)
75 e11 = 100 * (K11 - kref) / kref
76 e12 = 100 * (K12 - kref) / kref
77 o11 = np.log(abs(prev["e11"]) / abs(e11)) / np.log(N / prev["N"]) if prev else float("nan")
78 o12 = np.log(abs(prev["e12"]) / abs(e12)) / np.log(N / prev["N"]) if prev else float("nan")
79 e22, i22 = G22[N]
80 print(f"{N:>4} | {CUT[N]:>+9.3f} | {e22:>+8.3f} {i22:>3d} | {e11:>+8.3f} {o11:>5.2f} {i11:>3d} "
81 f"{d11:>8.1e} | {e12:>+8.3f} {o12:>5.2f} {i12:>3d} {d12:>8.1e} | {t11 + t12:>4.0f}",
82 flush=True)
83 prev = {"e11": e11, "e12": e12, "N": N}
drag(N, morder, rorder, mu=0.1, F=1e-3, dt=80.0, warm_tol=1e-7, tail=40, max_steps=4000)
static double mean(const std::vector< double > &a)