flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
verify_colocated_spheres.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Phase-4 verification (collocated grid): Stokes flow through a periodic 2x2x2 sphere packing with the
3Robust-Scaled cut-cell IBM โ€” the first immersed-solid test of the collocated solver. A body force drives
4flow through the pores; the cut-cell overlay enforces no-slip at the cell-centered velocities and the
5shared cut-cell pressure operator + approximate (MAC) projection enforce incompressibility.
6
7This is the ยง4 gate of doc/sdflow_colocated_plan.md: the collocated permeability must (a) be incompressible
8(projected FACE field divergence-free to machine precision), (b) satisfy exact no-slip in the deep solid,
9and (c) converge โ€” across resolution AND toward the staggered solver, which is itself validated against the
10Zick & Homsy sphere-array drag. The plain area-weighted face averaging (Option A) + central-difference cell
11correction are tested here; escalation to the open-centroid reconstruction (Option B) / openness-aware cell
12gradient is only warranted if this gate fails.
13"""
14import os
15import sys
16
17import numpy as np
18
19sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", os.environ.get("SDFLOW_BUILD", "build"))))
20from peclet import flow as sdflow # noqa: E402
21
22
23def packing_sdf(N, radius_frac=0.18):
24 R = N * radius_frac
25 gx = np.arange(N)
26 cs = [(c + 0.5) * N / 2.0 for c in (0, 1)]
27 X, Y, Z = np.meshgrid(gx, gx, gx, indexing="ij")
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 np.asfortranarray(best), R
37
38
39def run(SolverCls, N, mu=0.1, dt=60.0, F=1e-3, steps=400):
40 sdf, R = packing_sdf(N)
41 s = SolverCls(N, N, N)
42 s.set_rho(1.0)
43 s.set_mu(mu)
44 s.set_dt(dt)
45 s.set_body_force(F, 0.0, 0.0)
46 s.set_advection(False) # creeping (Stokes) flow
47 s.set_velocity_solver_params(80)
48 s.set_pressure_pcg(True, 500, 1e-10)
49 s.set_solid(sdf, cutcell_pressure=True)
50
51 prev = 0.0
52 for it in range(steps):
53 s.step()
54 um = float(s.get_u().mean())
55 if it > 8 and abs(um - prev) < 3e-4 * (abs(um) + 1e-15):
56 break
57 prev = um
58
59 u = s.get_u()
60 k = mu * float(u.mean()) / F
61 u_solid = float(np.abs(u[sdf < -2.0]).max())
62 return k, u_solid, float(s.max_open_divergence()), float(u.max())
63
64
65def main():
66 Ns = (32, 48, 64)
67 print("=== sdflow phase-4: collocated cut-cell IBM โ€” Stokes permeability through a sphere packing ===")
68 print(f"{'N':>4} {'k_stag':>12} {'k_coloc':>12} {'rel.diff':>9} {'maxdiv_co':>11} {'u_solid_co':>11}")
69 rows = []
70 for N in Ns:
71 ks, _, _, _ = run(sdflow.Solver, N)
72 kc, usc, dc, umc = run(sdflow.SolverColocated, N)
73 rel = 100.0 * abs(kc - ks) / ks
74 print(f"{N:4d} {ks:12.5e} {kc:12.5e} {rel:8.2f}% {dc:11.2e} {usc:11.2e}")
75 rows.append((ks, kc, rel, dc, usc))
76
77 incompressible = all(r[3] < 1e-7 for r in rows)
78 no_slip = all(r[4] == 0.0 for r in rows)
79 k_converges = rows[0][1] < rows[1][1] < rows[2][1] # collocated k rises as spheres resolve
80 rel_shrinks = rows[2][2] < rows[0][2] # collocated -> staggered with resolution
81 ok = incompressible and no_slip and k_converges and rel_shrinks
82 print(f" incompressible (face div <1e-7): {incompressible}")
83 print(f" exact no-slip in deep solid: {no_slip}")
84 print(f" collocated k grid-converges: {k_converges} ({rows[0][1]:.4e} -> {rows[2][1]:.4e})")
85 print(f" collocated -> staggered with N: {rel_shrinks} ({rows[0][2]:.2f}% -> {rows[2][2]:.2f}%)")
86 print(f" result: {'PASS' if ok else 'FAIL'} (Option A area-weighting + central-diff cell correction)")
87 sys.exit(0 if ok else 1)
88
89
90if __name__ == "__main__":
91 main()
run(SolverCls, N, mu=0.1, dt=60.0, F=1e-3, steps=400)