flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
verify_anisotropic_spheres.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Phase 2 gate G2, the LADDER half (doc/anisotropic_metric.md §8.3).
3
4The regression suite's Zick & Homsy sphere (`tests/regression/sdflow_regression.py`, case
5`zh_sphere`: solid fraction phi = 0.216, R = 0.3722 L, K_ref = 7.442, config rho 1 / mu 0.1 /
6dt 60 / F 1e-3, 80 velocity sweeps, MG-PCG 300 / 1e-8, cut-cell pressure, advection off) on the
7cube `L^3` resolved by cells **(N, 2N, N/2)** -- h = (dx, dx/2, 2dx) -- with the SDF sampled at the
8PHYSICAL cell centres, beside the cubic control (N, N, N) in cell units at every rung.
9
10What it gates (§8.3, the `script` bullet):
11 * the Richardson-extrapolated `K_s,inf` is within **2 %** of 7.442 (the regression's `extrap_rel`);
12 * the fitted order of the stretched sequence `p_s` lies in **[1.5, 2.5]**;
13 * the pressure iterations per step on the stretched grid are **<= cubic + 2** at every N (the §5
14 aspect-ratio coarsening rule is what buys that).
15
16The order/extrapolation estimator is the regression's own `fit_order` (f(N) = f_inf + C N^-p), so
17the cubic column is directly comparable to `perf_baseline.json`'s recorded `order` / `extrapolated`.
18
19Usage (from the repo root, venv active):
20 PYTHONPATH=build python scripts/verify_anisotropic_spheres.py
21 PECLET_FLOW_BUILD=build python scripts/verify_anisotropic_spheres.py --grids 16,24,32
22"""
23import argparse
24import os
25import sys
26import time
27
28import numpy as np
29
30from _bootstrap import ensure_flow # noqa: E402
31sdflow = ensure_flow()
32
33sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "tests",
34 "regression")))
35from sdflow_regression import CFG, fit_order, zh_ref # noqa: E402
36
37PHI = 0.216
38K_REF = zh_ref(PHI) # 7.442
39
40
41def run(nx, ny, nz, arm, L, cfg):
42 """One rung. `arm=False` is the cell-unit cubic control; `arm=True` arms the cube L^3."""
43 R = (PHI * 3.0 / (4.0 * np.pi)) ** (1.0 / 3.0) * L
44 levels = max(2, int(np.floor(np.log2(nx))) - 1)
45 kw = {} if not arm else {"extent": (L, L, L), "origin": (0.0, 0.0, 0.0)}
46 s = sdflow.Solver((nx, ny, nz), **kw)
47 s.set_rho(cfg["rho"]); s.set_mu(cfg["mu"]); s.set_dt(cfg["dt"])
48 s.set_body_force((cfg["F"], 0.0, 0.0))
49 s.set_advection(False)
50 s.diagnostics.set_velocity_solver_params(cfg["vel_sweeps"])
51 s.set_pressure_multigrid(True, levels=levels)
52 s.set_pressure_pcg(True, cfg["pcg_maxit"], cfg["pcg_rtol"])
53 cx, cy, cz = s.cell_centers()
54 c = 0.5 * L
55 X, Y, Z = np.meshgrid(cx, cy, cz, indexing="ij")
56 sdf = np.sqrt((X - c) ** 2 + (Y - c) ** 2 + (Z - c) ** 2) - R
57 s.set_solid(np.asfortranarray(sdf), cutcell_pressure=True)
58
59 t0 = time.time()
60 prev, steps, p_iters = 0.0, 0, []
61 for it in range(cfg["max_steps"]):
62 s.step()
63 steps += 1
64 p_iters.append(s.diagnostics.last_pressure_iterations())
65 if it % cfg["check_every"] == cfg["check_every"] - 1:
66 m = float(s.get_u().mean())
67 if it >= cfg["min_steps"] and abs(m - prev) < cfg["conv_tol"] * (abs(m) + 1e-30):
68 break
69 prev = m
70 wall = time.time() - t0
71 umean = float(s.get_u().mean())
72 K = cfg["F"] * L ** 3 / (6.0 * np.pi * cfg["mu"] * R * umean)
73 half = p_iters[len(p_iters) // 2:]
74 return {"K": K, "iters": float(np.median(half)), "steps": steps,
75 "div": float(s.max_open_divergence()), "spacing": tuple(s.spacing),
76 "aniso": len(set(s.spacing)) > 1, "wall": wall}
77
78
79def main():
80 ap = argparse.ArgumentParser()
81 ap.add_argument("--grids", default="16,24,32,48,64")
82 a = ap.parse_args()
83 grids = [int(g) for g in a.grids.split(",")]
84 cfg = dict(CFG)
85
86 print(f"Zick & Homsy sphere, phi = {PHI}, K_ref = {K_REF}")
87 print(f"{'N':>4} {'cells (stretched)':>20} {'spacing':>20} "
88 f"{'K_cubic':>10} {'K_stretch':>10} {'err_c %':>9} {'err_s %':>9} "
89 f"{'it_c':>5} {'it_s':>5} {'steps_s':>8}")
90 Kc, Ks, itc, its = [], [], [], []
91 ok = True
92 for N in grids:
93 c = run(N, N, N, False, float(N), cfg)
94 s = run(N, 2 * N, N // 2, True, float(N), cfg)
95 Kc.append(c["K"]); Ks.append(s["K"]); itc.append(c["iters"]); its.append(s["iters"])
96 ec = 100.0 * abs(c["K"] - K_REF) / K_REF
97 es = 100.0 * abs(s["K"] - K_REF) / K_REF
98 sp = "(%.4g, %.4g, %.4g)" % s["spacing"]
99 print(f"{N:>4} {f'{N}x{2*N}x{N//2}':>20} {sp:>20} "
100 f"{c['K']:>10.5f} {s['K']:>10.5f} {ec:>9.4f} {es:>9.4f} "
101 f"{c['iters']:>5.1f} {s['iters']:>5.1f} {s['steps']:>8d}")
102 if not s["aniso"]:
103 print(" FAIL: the stretched rung did not arm an anisotropic metric")
104 ok = False
105 if s["iters"] > c["iters"] + 2:
106 print(f" FAIL: pressure iters/step {s['iters']} > cubic {c['iters']} + 2")
107 ok = False
108
109 pc, Kc_inf = fit_order(grids, Kc)
110 ps, Ks_inf = fit_order(grids, Ks)
111 ec_inf = abs(Kc_inf - K_REF) / K_REF
112 es_inf = abs(Ks_inf - K_REF) / K_REF
113 print()
114 print(f" cubic order p = {pc:.4f} K_inf = {Kc_inf:.5f} |K_inf - K_ref|/K_ref = "
115 f"{100*ec_inf:.4f} %")
116 print(f" stretched order p = {ps:.4f} K_inf = {Ks_inf:.5f} |K_inf - K_ref|/K_ref = "
117 f"{100*es_inf:.4f} %")
118 print()
119 if not (es_inf <= 0.02):
120 print(f" FAIL: extrapolated K_s,inf off by {100*es_inf:.4f} % (bound 2 %)"); ok = False
121 if not (1.5 <= ps <= 2.5):
122 print(f" FAIL: stretched order {ps:.4f} outside [1.5, 2.5]"); ok = False
123 print("PASS" if ok else "FAIL")
124 return 0 if ok else 1
125
126
127if __name__ == "__main__":
128 sys.exit(main())
static double mean(const std::vector< double > &a)