flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
verify_velocity_mg_staircase_packing_sdflow.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Staircase velocity-MG on a REAL packed bed (random periodic sphere packing, thin pore necks).
3
4The SC single sphere is one connected solid blob; a packed bed has MANY solid spheres with thin fluid necks
5between near-contacts -- the geometry that stresses the coarse operator (the const-coarse couples fluid
6pockets through resolved walls; the staircase classification should disconnect them). This checks that the
7staircase velocity-MG (a) gives the EXACT RB-GS mean velocity / permeability on a packed bed, (b) stays stable
8at large dt, and (c) how the coarsening-level cap matters (deep coarsening dissolves the thin necks).
9"""
10import os
11import sys
12
13import numpy as np
14
15sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..",
16 os.environ.get("SDFLOW_BUILD", "build_mpi"))))
17from peclet import flow as sdflow # noqa: E402
18
19
20def packing_sdf(N, R, target_phi, gap=2.0, seed=0):
21 """Random periodic sphere packing: greedily place non-overlapping spheres (min surface gap `gap` cells)
22 until the solid fraction reaches target_phi. SDF = min over spheres of (dist - R), periodic, <0 in solid."""
23 rng = np.random.default_rng(seed)
24 g = np.arange(N) + 0.5
25 X, Y, Z = np.meshgrid(g, g, g, indexing="ij")
26 sdf = np.full((N, N, N), 1e30)
27 centers = []
28 vsph = 4.0 / 3.0 * np.pi * R ** 3
29 while (len(centers) * vsph) / N ** 3 < target_phi:
30 placed = False
31 for _ in range(2000):
32 c = rng.random(3) * N
33 ok = True
34 for cc in centers:
35 d = c - cc; d -= N * np.round(d / N)
36 if np.linalg.norm(d) < 2 * R + gap:
37 ok = False; break
38 if ok:
39 centers.append(c); placed = True; break
40 if not placed:
41 break
42 for c in centers:
43 dx = X - c[0]; dx -= N * np.round(dx / N)
44 dy = Y - c[1]; dy -= N * np.round(dy / N)
45 dz = Z - c[2]; dz -= N * np.round(dz / N)
46 sdf = np.minimum(sdf, np.sqrt(dx * dx + dy * dy + dz * dz) - R)
47 return sdf, len(centers), float((sdf < 0).mean())
48
49
50def run(sdf, mode, dt, vcyc=16, levels=4, mu=0.1, f=1e-3, steps=600, tol=1e-7):
51 """mode: 'rbgs' (plain RB-GS reference) | 'vmg' (the staircase velocity-MG, via set_velocity_multigrid)."""
52 N = sdf.shape[0]
53 s = sdflow.Solver(N, N, N)
54 s.set_rho(1.0); s.set_mu(mu); s.set_dt(dt); s.set_body_force(f, 0, 0); s.set_advection(False)
55 if mode == "rbgs":
56 s.set_velocity_solver_params(400)
57 else:
58 s.set_velocity_multigrid(True, levels, vcyc) # IBM -> staircase coarse op (the default)
59 plv = max(2, int(np.log2(N)) - 1)
60 s.set_pressure_multigrid(True, levels=plv); s.set_pressure_pcg(True, max_iter=300, rtol=1e-9)
61 s.set_solid(sdf, cutcell_pressure=True, pressure_coarse="rediscretized")
62 prev = 0.0
63 for it in range(steps):
64 s.step()
65 if not np.isfinite(s.get_u()).all():
66 return None
67 if it % 5 == 4:
68 m = float(s.get_u().mean())
69 if it > 10 and abs(m - prev) < tol * (abs(m) + 1e-30):
70 break
71 prev = m
72 return float(s.get_u().mean()) # superficial velocity ~ permeability (compared across methods)
73
74
75def main():
76 N = int(os.environ.get("VMG_N", 64))
77 R = float(os.environ.get("VMG_R", 9.0))
78 phi = float(os.environ.get("VMG_PHI", 0.35))
79 gap = float(os.environ.get("VMG_GAP", 2.0))
80 sdf, nsph, phi_act = packing_sdf(N, R, phi, gap=gap, seed=int(os.environ.get("VMG_SEED", 1)))
81 print(f"=== packed bed: {nsph} spheres R={R:.0f} in {N}^3, solid phi={phi_act:.3f}, gap~{gap:g} cells ===")
82
83 uref = run(sdf, "rbgs", dt=60.0)
84 print(f" RB-GS reference: U_sup={uref:.6e}")
85 print("--- staircase velocity-MG (set_velocity_multigrid): exact vs RB-GS + large-dt stability ---")
86 for dt in [60.0, 800.0, 3200.0]:
87 u = run(sdf, "vmg", dt=dt, levels=4)
88 e = "NaN" if u is None else f"{abs(u - uref) / abs(uref) * 100:.3f}%"
89 print(f" vmg dt={dt:6.0f} (beta={mu_b(dt):4.0f}): {'BLEW UP' if u is None else f'U_sup={u:.6e} err {e}'}")
90
91 print("--- coarsening-level robustness (deep levels dissolve thin necks), dt=60 ---")
92 for lv in [2, 3, 4, 5, 6]:
93 u = run(sdf, "vmg", dt=60.0, levels=lv)
94 e = "NaN" if u is None else f"{abs(u - uref) / abs(uref) * 100:.3f}%"
95 print(f" levels={lv}: {'BLEW UP' if u is None else f'err {e}'}")
96
97
98def mu_b(dt, mu=0.1):
99 return mu * dt
100
101
102if __name__ == "__main__":
103 main()
run(sdf, mode, dt, vcyc=16, levels=4, mu=0.1, f=1e-3, steps=600, tol=1e-7)