flow 0.4.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.abspath(os.environ.get("SDFLOW_BUILD", "build_cuda2")))
17import numpy as np
18from peclet import flow
19
20HERE = os.path.dirname(os.path.abspath(__file__))
21
22
23def sdf_from_pack(Ng, pos, r, side):
24 g = (np.arange(Ng) + 0.5) / Ng * side
25 X, Y, Z = np.meshgrid(g, g, g, indexing="ij")
26 best = np.full((Ng, Ng, Ng), 1e30)
27 for k in range(len(pos)):
28 dx = X - (pos[k, 0] + side / 2)
29 dx -= side * np.round(dx / side)
30 dy = Y - (pos[k, 1] + side / 2)
31 dy -= side * np.round(dy / side)
32 dz = Z - (pos[k, 2] + side / 2)
33 dz -= side * np.round(dz / side)
34 best = np.minimum(best, np.sqrt(dx * dx + dy * dy + dz * dz) - r[k])
35 return best
36
37
38def permeability(Ng, sdf, side, colloc, ghost, mode=0, mu=0.1, F=1e-3, dt=80.0, max_steps=3000,
39 tol=1e-6):
40 lv = max(2, int(np.log2(Ng)) - 1)
41 s = (flow.SolverColocated if colloc else flow.Solver)(Ng, Ng, Ng)
42 s.set_rho(1.0)
43 s.set_mu(mu)
44 s.set_dt(dt)
45 s.set_body_force(F, 0, 0)
46 s.set_advection(False)
47 s.set_velocity_solver_params(150)
48 s.set_pressure_multigrid(True, levels=lv)
49 s.set_pressure_pcg(True, 400, 1e-9)
50 if ghost:
51 s.set_ghost_projection(True, 1, 2)
52 if mode:
53 s.set_face_interp(mode)
54 s.set_solid(np.asfortranarray(sdf), cutcell_pressure=True,
55 pressure_coarse="rediscretized")
56 prev = 0.0
57 for it in range(max_steps):
58 s.step()
59 if it % 5 == 4:
60 m = float(s.get_u().mean())
61 if it > 10 and abs(m - prev) < tol * (abs(m) + 1e-30):
62 break
63 prev = m
64 umean = float(s.get_u().mean())
65 return dict(k=mu * umean / F * (side / Ng) ** 2, steps=it + 1,
66 pit=s.last_pressure_iterations(), div=s.max_open_divergence())
67
68
69if __name__ == "__main__":
70 d = np.load(os.path.join(HERE, "rcp_pack_seed3.npz"))
71 pos, r, side, phi = d["pos"], d["r"], float(d["side"]), float(d["phi"])
72 print(f"RCP: N={len(pos)} phi={phi:.4f} side={side:.3f}", flush=True)
73 # mode 10 (open-centroid quadrature) is deliberately absent: it DIVERGES on the RCP slivers
74 # (k -> 1e18, non-finite MG) — the mode-3a non-telescoping row-sum runaway; measured 2026-07-18.
75 variants = [("stag cutcell", False, False, 0), ("stag ghost", False, True, 0),
76 ("col cutcell", True, False, 0), ("col ghost", True, True, 0),
77 ("col hyb9", True, False, 9)]
78 print("columns: " + " | ".join(n for n, *_ in variants)
79 + " (k, % vs stag-cutcell, PCG/BiCGStab iters)", flush=True)
80 for Ng in (32, 44, 56):
81 sdf = sdf_from_pack(Ng, pos, r, side)
82 row = f"{Ng:>4} |"
83 kref = None
84 for name, colloc, ghost, mode in variants:
85 t0 = time.time()
86 out = permeability(Ng, sdf, side, colloc, ghost, mode)
87 if kref is None:
88 kref = out["k"]
89 row += (f" {out['k']:.4e} {100 * (out['k'] / kref - 1):+6.1f}%"
90 f" i{out['pit']:>3} |")
91 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)