flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
collocated_zh_ghostproj.py
Go to the documentation of this file.
1"""THE collocated ghost-projection experiment: Zick & Homsy drag, SolverColocated mode-0
2(plain average + central grad(P), 1st order) vs the directional ghost-cell projection
3(set_ghost_projection: ghost-closed divergence of the face-averaged field + gpCenterGrad
4predictor/correction). Success = ghost order ~2 converging to the same Z&H limit — closing the
5open problem of doc/collocated_second_order_open_problem.md. Pattern:
6tests/study/staggered_zh_ghostproj.py (same warm-detector + tail protocol as the staggered
7experiment and the mode-0 baselines)."""
8import os
9import sys
10import time
11
12sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "scripts"))
13from _bootstrap import ensure_flow # noqa: E402
14flow = ensure_flow()
15
16import numpy as np
17
18def lattice_sdf(N, phi=0.125):
19 R = (3 * phi / (4 * np.pi)) ** (1 / 3) * N
20 g = np.arange(N) + 0.5
21 X, Y, Z = np.meshgrid(g, g, g, indexing="ij")
22 dx = X - 0.5 * N
23 dx -= N * np.round(dx / N)
24 dy = Y - 0.5 * N
25 dy -= N * np.round(dy / N)
26 dz = Z - 0.5 * N
27 dz -= N * np.round(dz / N)
28 return np.sqrt(dx * dx + dy * dy + dz * dz) - R, R
29
30
31GPORD = tuple(int(v) for v in os.environ.get("GPORD", "1,2").split(","))
32
33def drag(N, ghost, orders=None, mu=0.1, F=1e-3, dt=80.0, warm_tol=1e-7, tail=40,
34 max_steps=4000):
35 sdf, R = lattice_sdf(N)
36 lv = max(2, int(np.log2(N)) - 1)
37 s = flow.SolverColocated(N, N, N)
38 s.set_rho(1.0)
39 s.set_mu(mu)
40 s.set_dt(dt)
41 s.set_body_force((F, 0, 0))
42 s.set_advection(False)
43 s.diagnostics.set_velocity_solver_params(200)
44 s.set_pressure_multigrid(True, levels=lv)
45 s.set_pressure_pcg(True, 400, 1e-10)
46 if ghost:
47 s.diagnostics.set_ghost_projection(True, *(orders or GPORD))
48 s.set_solid(sdf, cutcell_pressure=True)
49 prev, warm, um, t0 = 0.0, None, [], time.time()
50 for it in range(max_steps):
51 s.step()
52 m = float(s.get_u().mean())
53 um.append(m)
54 if warm is None:
55 if it % 10 == 9:
56 if it > 10 and abs(m - prev) < warm_tol * (abs(m) + 1e-30):
57 warm = it
58 prev = m
59 elif it - warm >= tail:
60 break
61 K = F * N**3 / (6 * np.pi * mu * R * np.mean(um[-tail:]))
62 return K, it + 1, s.diagnostics.last_pressure_iterations(), s.max_open_divergence(), time.time() - t0
63
64
65if __name__ == "__main__":
66 kref = 4.2920
67 print(f"Z&H K={kref}. Collocated: mode-0 (baseline, 1st order) vs directional ghost-cell "
68 f"projection (1,2).", flush=True)
69 print(f"{'N':>4} | {'mode0 err%':>10} {'ord':>6} | {'ghost err%':>11} {'ord':>6} | "
70 f"{'it_0':>4} {'it_g':>4} | {'div_g':>9} | secs", flush=True)
71 prev = {}
72 for N in (32, 48, 64, 96, 128):
73 Kc, sc, ipc, dvc, tc = drag(N, ghost=False)
74 Kg, sg, ipg, dvg, tg = drag(N, ghost=True)
75 ec = 100 * (Kc - kref) / kref
76 eg = 100 * (Kg - kref) / kref
77 oc = np.log(abs(prev["c"]) / abs(ec)) / np.log(N / prev["N"]) if prev else float("nan")
78 og = np.log(abs(prev["g"]) / abs(eg)) / np.log(N / prev["N"]) if prev else float("nan")
79 print(f"{N:>4} | {ec:>+10.3f} {oc:>6.2f} | {eg:>+11.3f} {og:>6.2f} | "
80 f"{ipc:>4d} {ipg:>4d} | {dvg:>9.2e} | {tc + tg:>4.0f}", flush=True)
81 prev = {"c": ec, "g": eg, "N": N}
drag(N, ghost, orders=None, 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)