flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
three_solver_study.py
Go to the documentation of this file.
1"""sdflow side of the three-solver study: staggered vs collocated, Z&H SC sphere + random packing,
2Re=0 (Stokes) and Re=100 (advection on). Prints K (Z&H) / k* (random), cells, pressure iters, wall.
3Geometry + metrics identical to tests/regression/sdflow_regression.py so AMR can be compared directly.
4Pair with transport-core/python/amr_drag_study.py for the AMR (graded cut-cell) side.
5
6Findings (2026-06-25):
7 * Z&H phi=0.216 (ref K=7.442), Re=0: staggered converges 2nd-order from below
8 (N=16 -1.78% -> N=48 -0.07%); collocated carries the intrinsic +~1% gap (N=32 +1.16%).
9 * random pack k*: staggered -> ~0.00622 from above, collocated -> ~0.00618 from below (same k_inf).
10 * Re~100 (F=2.6e-3, N=32): staggered K=8.90 (Re=101.6), collocated K=9.05 (Re=99.9), ~+20% over
11 Stokes; both converge cleanly (div~1e-12). Same F at N=48 gives Re~300 (R scales with N) -- only
12 N=32 is a true Re=100 comparison.
13 * Staggered is the accuracy default for permeability/drag; collocated trades ~1%/grid for
14 cell-centered storage. (Kokkos/OpenMP: ~1-30 s per case.)
15"""
16import os, sys, time
17import numpy as np
18sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", os.environ.get("SDFLOW_BUILD", "build_omp"))))
19from peclet import flow as sdflow
20
21ZH_PHI = [0.000125,0.001,0.008,0.027,0.064,0.125,0.216,0.343,0.45,0.5236]
22ZH_K = [1.096,1.212,1.525,2.008,2.810,4.292,7.442,15.4,28.1,42.1]
23zh_ref = lambda phi: float(np.interp(phi, ZH_PHI, ZH_K))
24
25def _grid(N):
26 g=np.arange(N)+0.5; return np.meshgrid(g,g,g,indexing="ij")
27def _mm(d,N): return d-N*np.round(d/N)
28
29def sdf_zh(N, phi=0.216):
30 R=(phi*3/(4*np.pi))**(1/3)*N; X,Y,Z=_grid(N); c=N/2
31 return np.asfortranarray(np.sqrt((X-c)**2+(Y-c)**2+(Z-c)**2)-R), R
32
33def sdf_random(N, n=8, r_frac=0.18, jit=0.06, seed=12345):
34 rng=np.random.default_rng(seed); R=r_frac*N
35 base=np.array([[(i+.5)/2,(j+.5)/2,(k+.5)/2] for i in range(2) for j in range(2) for k in range(2)])
36 ctr=((base+jit*rng.standard_normal(base.shape))%1.0)*N
37 X,Y,Z=_grid(N); sdf=np.full((N,N,N),1e30)
38 for cx,cy,cz in ctr:
39 sdf=np.minimum(sdf,np.sqrt(_mm(X-cx,N)**2+_mm(Y-cy,N)**2+_mm(Z-cz,N)**2)-R)
40 return np.asfortranarray(sdf), R
41
42def run(case, N, solver="staggered", re=0.0, mu=0.1, F=1e-3, dt=60.0, max_steps=500, tol=1e-6):
43 if case=="zh": sdf,R=sdf_zh(N); metric="K"
44 else: sdf,R=sdf_random(N); metric="k*"
45 lv=max(2,int(np.floor(np.log2(N)))-1)
46 Cls=sdflow.SolverColocated if solver=="colocated" else sdflow.Solver
47 s=Cls(N,N,N); s.set_rho(1.0); s.set_mu(mu); s.set_dt(dt); s.set_body_force(F,0,0)
48 s.set_advection(re>0.0)
49 if re>0: s.set_implicit_advection(True)
50 s.set_velocity_solver_params(80)
51 s.set_pressure_multigrid(True,levels=lv); s.set_pressure_pcg(True,300,1e-8)
52 s.set_solid(sdf,cutcell_pressure=True,pressure_coarse="rediscretized")
53 t0=time.time(); prev=0.0; steps=0; piters=[]
54 for it in range(max_steps):
55 s.step(); steps+=1; piters.append(s.last_pressure_iterations())
56 if it%5==4:
57 m=float(s.get_u().mean())
58 if it>=15 and abs(m-prev)<tol*(abs(m)+1e-30): break
59 prev=m
60 wall=time.time()-t0; u=s.get_u(); um=float(u.mean())
61 K=F*N**3/(6*np.pi*mu*R*um); kstar=mu*um/(F*N**2)
62 Re=1.0*abs(um)*2*R/mu
63 val=K if metric=="K" else kstar
64 rr={"N":N,"cells":N**3,"metric":metric,"val":val,"K":K,"kstar":kstar,"umean":um,
65 "Re":Re,"piters":int(np.median(piters[len(piters)//2:])),"steps":steps,
66 "div":float(s.max_open_divergence()),"wall":wall}
67 del s
68 return rr
69
70if __name__=="__main__":
71 import gc
72 cases = [("zh",[16,24,32,48]), ("random",[24,32,48])]
73 print("=== sdflow: staggered vs collocated, Re=0 (Stokes) ===",flush=True)
74 for case,grids in cases:
75 ref = zh_ref(0.216) if case=="zh" else None
76 print(f"\n[{case}] ref={ref}",flush=True)
77 for solver in ("staggered","colocated"):
78 for N in grids:
79 r=run(case,N,solver,re=0.0); gc.collect()
80 err = f"{100*(r['val']-ref)/ref:+.2f}%" if ref else ""
81 print(f" {solver:10s} N={N:3d} cells={r['cells']:6d} {r['metric']}={r['val']:.5g} {err:>8} "
82 f"piter={r['piters']:3d} steps={r['steps']:3d} div={r['div']:.1e} {r['wall']:.1f}s",flush=True)
83 print("\n=== sdflow: Re~100 (advection on), Z&H phi=0.216 ===",flush=True)
84 for solver in ("staggered","colocated"):
85 for N in [32,48]:
86 r=run("zh",N,solver,re=100.0,F=5e-2,dt=20.0,max_steps=800); gc.collect()
87 print(f" {solver:10s} N={N:3d} K={r['K']:.4f} Re={r['Re']:.1f} steps={r['steps']} "
88 f"div={r['div']:.1e} {r['wall']:.1f}s",flush=True)
run(case, N, solver="staggered", re=0.0, mu=0.1, F=1e-3, dt=60.0, max_steps=500, tol=1e-6)
sdf_random(N, n=8, r_frac=0.18, jit=0.06, seed=12345)