flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
verify_periodic_spheres_sdflow.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Verification (sdflow): creeping (Stokes) flow through a periodic sphere packing -- the porous-media
3target case. A body force drives flow through the pore space; the Robust-Scaled cut-cell IBM enforces
4no-slip on the spheres and the cut-cell pressure operator (Galerkin multigrid + CG) enforces
5incompressibility. We report the Darcy permeability k = mu*<u>/F and check:
6 * the flow is incompressible (small cut-cell flux divergence),
7 * no-slip holds (velocity ~ 0 inside the solid),
8 * the permeability is finite/positive and increases as the spheres resolve.
9
10Uses the canonical `sdflow` module (one GPU as plain `python`, or multi-rank under `mpirun -np N python`).
11Physical units: set_rho/set_mu, body force F is a force per unit volume (= -dp/dx). Grid units (dx = 1).
12"""
13import sys
14
15import numpy as np
16
17from _bootstrap import ensure_flow # noqa: E402
18sdflow = ensure_flow()
19
20
21def packing_sdf(N, radius_frac=0.18):
22 """2x2x2 sphere packing as a 3-D array sdf[x,y,z]; negative inside the spheres (min-image)."""
23 R = N * radius_frac
24 gx = np.arange(N)
25 cs = [(cx + 0.5) * N / 2.0 for cx in (0, 1)] # centres of the 2x2x2 lattice
26 X, Y, Z = np.meshgrid(gx, gx, gx, indexing="ij") # (N,N,N) indexed [x,y,z]
27 best = np.full((N, N, N), 1e30)
28 for sx in cs:
29 for sy in cs:
30 for sz in cs:
31 dx = X - sx; dx -= N * np.round(dx / N)
32 dy = Y - sy; dy -= N * np.round(dy / N)
33 dz = Z - sz; dz -= N * np.round(dz / N)
34 best = np.minimum(best, np.sqrt(dx * dx + dy * dy + dz * dz) - R)
35 return best, R
36
37
38def run(N, rho=1.0, mu=0.1, dt=60.0, F=1e-3, max_steps=200):
39 sdf, R = packing_sdf(N) # 3-D sdf[x,y,z]
40 porosity = 1.0 - 8.0 * (4.0 / 3.0 * np.pi * R**3) / N**3
41
42 s = sdflow.Solver(N, N, N)
43 s.set_rho(rho)
44 s.set_mu(mu)
45 s.set_dt(dt)
46 s.set_body_force((F, 0.0, 0.0))
47 s.set_advection(False) # creeping (Stokes) flow
48 # Default solver: simple Red-Black Gauss-Seidel (matches pnm_backend's approach and per-step speed;
49 # the Galerkin-multigrid/PCG path stays available via set_pressure_pcg for stiff cases).
50 s.diagnostics.set_velocity_solver_params(80) # IBM RB-GS velocity sweeps
51 s.set_pressure_solver_params(20) # RB-GS sweeps on the cut-cell operator
52 s.set_pressure_multigrid(True, levels=1) # 1 level == pure RB-GS (keeps the operator)
53 s.set_solid(sdf, cutcell_pressure=True) # no-slip + cut-cell pressure operator
54
55 deep_solid = sdf < -2.0 # cells whose every velocity face is solid -> must be exactly no-slip
56 prev = 0.0
57 for it in range(max_steps):
58 s.step() # large dt -> backward Euler approaches the steady Stokes solve
59 u = s.get_u() # collective gather: ALL ranks must call it
60 converged = False
61 if s.rank() == 0:
62 umean = float(u.mean())
63 converged = it > 8 and abs(umean - prev) < 3e-4 * (abs(umean) + 1e-15)
64 prev = umean
65 if s.diagnostics.bcast_from_root(converged):
66 break
67
68 u = s.get_u() # collective
69 div = s.max_open_divergence() # collective (Allreduce) -- all ranks must call it
70 if s.rank() != 0:
71 return None
72 k = mu * float(u.mean()) / F # Darcy permeability (grid units)
73 u_solid = float(np.abs(u[deep_solid]).max()) # no-slip check (deep solid)
74 return N, porosity, k, u_solid, float(u.max()), div
75
76
77def main():
78 # All ranks must call run() for every N (collective steps/gathers); only root has results.
79 results = [run(N) for N in (32, 64)]
80 if results[0] is None: # non-root rank
81 return
82 print("=== sdflow: Stokes flow through a periodic sphere packing (rho/mu units) ===")
83 print(f"{'N':>4} {'porosity':>9} {'permeability k':>15} {'max|u|solid':>12} {'max fluxdiv':>12}")
84 ks = []
85 for N_, phi, k, us, umax, div in results:
86 print(f"{N_:4d} {phi:9.3f} {k:15.6e} {us:12.3e} {div:12.3e}")
87 ks.append((k, us, umax, div))
88 k32, k64 = ks[0][0], ks[1][0]
89 incompressible = all(d < 1e-6 * umax for (_, _, umax, d) in ks)
90 no_slip = all(us == 0.0 for (_, us, _, _) in ks)
91 sensible = 0.0 < k32 < k64 and np.isfinite(k64)
92 ok = incompressible and no_slip and sensible
93 print(f" permeability k(N=32)={k32:.3e} k(N=64)={k64:.3e} (rises as the spheres resolve)")
94 print(f" incompressible={incompressible} no-slip(exact in solid)={no_slip} sensible-k={sensible}")
95 print(f" result: {'PASS' if ok else 'FAIL'} (incompressible Stokes flow, exact no-slip on SDF spheres)")
96 sys.exit(0 if ok else 1)
97
98
99if __name__ == "__main__":
100 main()
run(N, rho=1.0, mu=0.1, dt=60.0, F=1e-3, max_steps=200)