flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
rcp_permeability_ab.py
Go to the documentation of this file.
1"""RCP permeability A/B: {staggered, collocated} x {cutcell, ghost} on the SAME random close
2packing (the peclet-examples random-packed-bed geometry: dem pack_bed, N=180, phi~0.63, seed 3;
3cached in rcp_pack_seed3.npz — regenerate with the example's pack_bed if absent).
4
5Context: the staggered examples-check found the ghost projection inflates the RCP permeability
6(+21/+15/+12% at Ng=32/44/56 vs the cutcell reference) because point-based closure faces cannot
7throttle sub-cell throats the way apertures do, and the binary COUPLED graph fragments (guard
8decouples the pockets). This study measures whether the collocated ghost inherits the same
9defect, with the collocated cutcell (mode 0) and both staggered variants as anchors. Protocol =
10the example's steady loop with a tighter stop (tol 1e-6) so the scheme, not the stop criterion,
11is compared."""
12import os
13import sys
14import time
15
16sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "scripts"))
17from _bootstrap import ensure_flow # noqa: E402
18flow = ensure_flow()
19
20import numpy as np
21HERE = os.path.dirname(os.path.abspath(__file__))
22
23
24
25def _collocated_mode(s, mode):
26 """Select a collocated scheme by the integer face-interp mode this script was written with:
27 0/7/9 are the public strings ('plain' / 'embed' / 'gauge-exact'), 5/6 the diagnostics rungs,
28 and every other number was deleted at 1.0.0 (the call then raises)."""
29 names = {0: "plain", 7: "embed", 9: "gauge-exact"}
30 if mode in names:
31 s.set_collocated_scheme(names[mode])
32 else:
33 s.diagnostics.set_face_interp(mode)
34
35def sdf_from_pack(Ng, pos, r, side):
36 g = (np.arange(Ng) + 0.5) / Ng * side
37 X, Y, Z = np.meshgrid(g, g, g, indexing="ij")
38 best = np.full((Ng, Ng, Ng), 1e30)
39 for k in range(len(pos)):
40 dx = X - (pos[k, 0] + side / 2)
41 dx -= side * np.round(dx / side)
42 dy = Y - (pos[k, 1] + side / 2)
43 dy -= side * np.round(dy / side)
44 dz = Z - (pos[k, 2] + side / 2)
45 dz -= side * np.round(dz / side)
46 best = np.minimum(best, np.sqrt(dx * dx + dy * dy + dz * dz) - r[k])
47 return best
48
49
50def permeability(Ng, sdf, side, colloc, ghost, mode=0, mu=0.1, F=1e-3, dt=80.0, max_steps=3000,
51 tol=1e-6):
52 lv = max(2, int(np.log2(Ng)) - 1)
53 s = (flow.SolverColocated if colloc else flow.Solver)(Ng, Ng, Ng)
54 s.set_rho(1.0)
55 s.set_mu(mu)
56 s.set_dt(dt)
57 s.set_body_force((F, 0, 0))
58 s.set_advection(False)
59 s.diagnostics.set_velocity_solver_params(150)
60 s.set_pressure_multigrid(True, levels=lv)
61 s.set_pressure_pcg(True, 400, 1e-9)
62 if ghost:
63 s.diagnostics.set_ghost_projection(True, 1, 2)
64 if mode:
65 _collocated_mode(s, mode)
66 s.set_solid(np.asfortranarray(sdf), cutcell_pressure=True)
67 prev = 0.0
68 for it in range(max_steps):
69 s.step()
70 if it % 5 == 4:
71 m = float(s.get_u().mean())
72 if it > 10 and abs(m - prev) < tol * (abs(m) + 1e-30):
73 break
74 prev = m
75 umean = float(s.get_u().mean())
76 return dict(k=mu * umean / F * (side / Ng) ** 2, steps=it + 1,
77 pit=s.diagnostics.last_pressure_iterations(), div=s.max_open_divergence())
78
79
80if __name__ == "__main__":
81 d = np.load(os.path.join(HERE, "rcp_pack_seed3.npz"))
82 pos, r, side, phi = d["pos"], d["r"], float(d["side"]), float(d["phi"])
83 print(f"RCP: N={len(pos)} phi={phi:.4f} side={side:.3f}", flush=True)
84 # mode 10 (open-centroid quadrature) is deliberately absent: it DIVERGES on the RCP slivers
85 # (k -> 1e18, non-finite MG) — the mode-3a non-telescoping row-sum runaway; measured 2026-07-18.
86 variants = [("stag cutcell", False, False, 0), ("stag ghost", False, True, 0),
87 ("col cutcell", True, False, 0), ("col ghost", True, True, 0),
88 ("col hyb9", True, False, 9)]
89 print("columns: " + " | ".join(n for n, *_ in variants)
90 + " (k, % vs stag-cutcell, PCG/BiCGStab iters)", flush=True)
91 for Ng in (32, 44, 56):
92 sdf = sdf_from_pack(Ng, pos, r, side)
93 row = f"{Ng:>4} |"
94 kref = None
95 for name, colloc, ghost, mode in variants:
96 t0 = time.time()
97 out = permeability(Ng, sdf, side, colloc, ghost, mode)
98 if kref is None:
99 kref = out["k"]
100 row += (f" {out['k']:.4e} {100 * (out['k'] / kref - 1):+6.1f}%"
101 f" i{out['pit']:>3} |")
102 print(row, flush=True)
permeability(Ng, sdf, side, colloc, ghost, mode=0, mu=0.1, F=1e-3, dt=80.0, max_steps=3000, tol=1e-6)
sdf_from_pack(Ng, pos, r, side)
static double mean(const std::vector< double > &a)