flow
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 os
14import sys
15
16import numpy as np
17
18sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", os.environ.get("SDFLOW_BUILD", "build_mpi"))))
19from peclet import flow as sdflow # noqa: E402
20
21
22def packing_sdf(N, radius_frac=0.18):
23 """2x2x2 sphere packing as a 3-D array sdf[x,y,z]; negative inside the spheres (min-image)."""
24 R = N * radius_frac
25 gx = np.arange(N)
26 cs = [(cx + 0.5) * N / 2.0 for cx in (0, 1)] # centres of the 2x2x2 lattice
27 X, Y, Z = np.meshgrid(gx, gx, gx, indexing="ij") # (N,N,N) indexed [x,y,z]
28 best = np.full((N, N, N), 1e30)
29 for sx in cs:
30 for sy in cs:
31 for sz in cs:
32 dx = X - sx; dx -= N * np.round(dx / N)
33 dy = Y - sy; dy -= N * np.round(dy / N)
34 dz = Z - sz; dz -= N * np.round(dz / N)
35 best = np.minimum(best, np.sqrt(dx * dx + dy * dy + dz * dz) - R)
36 return best, R
37
38
39def run(N, rho=1.0, mu=0.1, dt=60.0, F=1e-3, max_steps=200):
40 sdf, R = packing_sdf(N) # 3-D sdf[x,y,z]
41 porosity = 1.0 - 8.0 * (4.0 / 3.0 * np.pi * R**3) / N**3
42
43 s = sdflow.Solver(N, N, N)
44 s.set_rho(rho)
45 s.set_mu(mu)
46 s.set_dt(dt)
47 s.set_body_force(F, 0.0, 0.0)
48 s.set_advection(False) # creeping (Stokes) flow
49 # Default solver: simple Red-Black Gauss-Seidel (matches pnm_backend's approach and per-step speed;
50 # the Galerkin-multigrid/PCG path stays available via set_pressure_pcg for stiff cases).
51 s.set_velocity_solver_params(80) # IBM RB-GS velocity sweeps
52 s.set_pressure_solver_params(20) # RB-GS sweeps on the cut-cell operator
53 s.set_pressure_multigrid(True, levels=1) # 1 level == pure RB-GS (keeps the operator)
54 s.set_solid(sdf, cutcell_pressure=True, pressure_coarse="const") # no-slip + cut-cell pressure operator
55
56 deep_solid = sdf < -2.0 # cells whose every velocity face is solid -> must be exactly no-slip
57 prev = 0.0
58 for it in range(max_steps):
59 s.step() # large dt -> backward Euler approaches the steady Stokes solve
60 u = s.get_u() # collective gather: ALL ranks must call it
61 converged = False
62 if s.rank() == 0:
63 umean = float(u.mean())
64 converged = it > 8 and abs(umean - prev) < 3e-4 * (abs(umean) + 1e-15)
65 prev = umean
66 if s.bcast_from_root(converged):
67 break
68
69 u = s.get_u() # collective
70 div = s.max_open_divergence() # collective (Allreduce) -- all ranks must call it
71 if s.rank() != 0:
72 return None
73 k = mu * float(u.mean()) / F # Darcy permeability (grid units)
74 u_solid = float(np.abs(u[deep_solid]).max()) # no-slip check (deep solid)
75 return N, porosity, k, u_solid, float(u.max()), div
76
77
78def main():
79 # All ranks must call run() for every N (collective steps/gathers); only root has results.
80 results = [run(N) for N in (32, 64)]
81 if results[0] is None: # non-root rank
82 return
83 print("=== sdflow: Stokes flow through a periodic sphere packing (rho/mu units) ===")
84 print(f"{'N':>4} {'porosity':>9} {'permeability k':>15} {'max|u|solid':>12} {'max fluxdiv':>12}")
85 ks = []
86 for N_, phi, k, us, umax, div in results:
87 print(f"{N_:4d} {phi:9.3f} {k:15.6e} {us:12.3e} {div:12.3e}")
88 ks.append((k, us, umax, div))
89 k32, k64 = ks[0][0], ks[1][0]
90 incompressible = all(d < 1e-6 * umax for (_, _, umax, d) in ks)
91 no_slip = all(us == 0.0 for (_, us, _, _) in ks)
92 sensible = 0.0 < k32 < k64 and np.isfinite(k64)
93 ok = incompressible and no_slip and sensible
94 print(f" permeability k(N=32)={k32:.3e} k(N=64)={k64:.3e} (rises as the spheres resolve)")
95 print(f" incompressible={incompressible} no-slip(exact in solid)={no_slip} sensible-k={sensible}")
96 print(f" result: {'PASS' if ok else 'FAIL'} (incompressible Stokes flow, exact no-slip on SDF spheres)")
97 sys.exit(0 if ok else 1)
98
99
100if __name__ == "__main__":
101 main()
run(N, rho=1.0, mu=0.1, dt=60.0, F=1e-3, max_steps=200)