flow 0.4.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
zh_collocated_gap.py
Go to the documentation of this file.
1#!/usr/bin/env python
2"""Is the collocated permeability ceiling real? Measure the collocated-minus-staggered GAP on
3Zick & Homsy, where both solvers run the same harness on identical geometry.
4
5Measuring either solver against K_ZH = 4.292 is limited by the table's four figures (+-0.023 %).
6The GAP between two computations on the same grid has no such limit: the benchmark value cancels,
7so a ceiling of 0.3 % is resolvable far below its own size, and the question "does the gap decay
8under refinement or flatten" gets a clean answer.
9
10 SDFLOW_BUILD=build_ge python tests/study/zh_collocated_gap.py [N ...]
11"""
12import os
13import sys
14import time
15
16import numpy as np
17
18sys.path.insert(0, os.path.abspath(os.path.join(
19 os.path.dirname(__file__), "..", "..", os.environ.get("SDFLOW_BUILD", "build"))))
20from peclet import flow # noqa: E402
21
22PHI0, K_ZH = 0.125, 4.292
23
24
25def drag(N, kind, mu=0.1, F=1e-3, dt=80.0, warm_tol=1e-7, tail=40, max_steps=4000):
26 R = (3 * PHI0 / (4 * np.pi)) ** (1 / 3) * N
27 g = np.arange(N) + 0.5
28 X, Y, Z = np.meshgrid(g, g, g, indexing="ij")
29 d = lambda A: A - 0.5 * N - N * np.round((A - 0.5 * N) / N) # noqa: E731
30 sdf = np.asfortranarray(np.sqrt(d(X) ** 2 + d(Y) ** 2 + d(Z) ** 2) - R)
31 s = flow.Solver(N, N, N) if kind == "stag" else flow.SolverColocated(N, N, N)
32 s.set_rho(1.0); s.set_mu(mu); s.set_dt(dt)
33 s.set_body_force(F, 0, 0); s.set_advection(False)
34 s.set_velocity_solver_params(150)
35 s.set_pressure_multigrid(True, max(2, int(np.log2(N)) - 1))
36 s.set_pressure_pcg(True, 200, 1e-8)
37 if kind != "stag":
38 # set_collocated_scheme is new; fall back to the integer form so this runs against older
39 # builds too (the Snellius module predates it).
40 if hasattr(s, "set_collocated_scheme"):
41 s.set_collocated_scheme(kind)
42 else:
43 s.set_face_interp({"gauge-exact": 9, "plain": 0}[kind])
44 s.set_solid(sdf, cutcell_pressure=True, pressure_coarse="rediscretized")
45 prev, warm, um, t0 = 0.0, None, [], time.time()
46 for it in range(max_steps):
47 s.step()
48 um.append(float(s.get_u().mean()))
49 if warm is None:
50 if it % 10 == 9:
51 if it > 10 and abs(um[-1] - prev) < warm_tol * (abs(um[-1]) + 1e-30):
52 warm = it
53 prev = um[-1]
54 elif it - warm >= tail:
55 break
56 u = float(np.mean(um[-tail:]))
57 return F * N ** 3 / (6 * np.pi * mu * R * u), it + 1, time.time() - t0
58
59
60if __name__ == "__main__":
61 Ns = [int(x) for x in (sys.argv[1:] or [32, 48, 64, 96, 128, 192, 256])]
62 print(f"{'N':>5} {'h/R':>7} | {'K stag':>9} {'K gauge-ex':>11} | {'gap %':>8} {'order':>7} "
63 f"| {'err_stag %':>10} {'steps s/g':>12} {'secs':>7}")
64 prev = None
65 for N in Ns:
66 R = (3 * PHI0 / (4 * np.pi)) ** (1 / 3) * N
67 try:
68 ks, ns, ts = drag(N, "stag")
69 kg, ng, tg = drag(N, "gauge-exact")
70 except Exception as e:
71 print(f"{N:>5} FAILED: {type(e).__name__}: {str(e)[:60]}"); break
72 gap = 100.0 * (kg - ks) / ks
73 o = ""
74 if prev and gap != 0:
75 o = f"{np.log(abs(prev[1] / gap)) / np.log(N / prev[0]):+.2f}"
76 print(f"{N:>5} {1/R:>7.4f} | {ks:>9.4f} {kg:>11.4f} | {gap:>+8.4f} {o:>7} | "
77 f"{100*(ks-K_ZH)/K_ZH:>+10.3f} {f'{ns}/{ng}':>12} {ts+tg:>7.0f}", flush=True)
78 prev = (N, gap)
79 print("\ngap = 100*(K_collocated - K_staggered)/K_staggered on IDENTICAL geometry, so the")
80 print("Zick-Homsy table's own precision cancels out. A decaying gap means no ceiling (the bed")
81 print("ladders over-read two noisy rungs); a flat gap means the ceiling is real and lives in")
82 print("the momentum/correction side, since the constraint operator was exonerated a-priori.")
drag(N, kind, mu=0.1, F=1e-3, dt=80.0, warm_tol=1e-7, tail=40, max_steps=4000)