flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
validate_zick_homsy_sdflow.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""GROUND TRUTH: simple-cubic (SC) array of spheres, Stokes drag factor K vs Zick & Homsy (1982).
3
4A single sphere centred in a periodic cube is the classic SC lattice. Z&H give the semi-analytic Stokes
5drag factor K(c) (c = sphere solid fraction) to high accuracy -- the external reference for the sdflow
6cut-cell IBM. (Historically this also re-ran the retired pnm_backend reference solver; sdflow was validated
7bit-identical to it before its retirement, so only the external Z&H comparison remains.)
8
9 K = F_total / (6*pi*mu*R*U_sup), F_total = f * V_cell, U_sup = mean(u) over the whole cell.
10
11K is dimensionless and unit-invariant.
12
13Usage: python scripts/validate_zick_homsy_sdflow.py [N1,N2,...] [phi1,phi2,...]
14"""
15import sys
16import time
17
18import numpy as np
19
20from _bootstrap import ensure_flow # noqa: E402
21sdflow = ensure_flow()
22
23# Zick & Homsy (1982), simple cubic: solid fraction c -> drag factor K.
24ZH_PHI = [0.000125, 0.001, 0.008, 0.027, 0.064, 0.125, 0.216, 0.343, 0.45, 0.5236]
25ZH_K = [1.096, 1.212, 1.525, 2.008, 2.810, 4.292, 7.442, 15.4, 28.1, 42.1]
26
27
28def zh_ref(phi):
29 return float(np.interp(phi, ZH_PHI, ZH_K))
30
31
32def sphere_radius(phi, N):
33 """Sphere radius (grid units) for solid fraction phi in an N^3 cube."""
34 return (phi * 3.0 / (4.0 * np.pi)) ** (1.0 / 3.0) * N
35
36
37def sc_sdf_xyz(N, phi):
38 """SC single sphere, SDF as 3-D array sdf[x,y,z] in grid units (dx=1); negative inside the sphere."""
39 R = sphere_radius(phi, N)
40 g = np.arange(N) + 0.5 # cell centres
41 X, Y, Z = np.meshgrid(g, g, g, indexing="ij")
42 c = N / 2.0
43 return np.sqrt((X - c) ** 2 + (Y - c) ** 2 + (Z - c) ** 2) - R, R
44
45
46def drag_K(umean, R, N, f, mu):
47 """K = F_total/(6 pi mu R U_sup), F_total = f*N^3 (grid units), U_sup = mean(u)."""
48 return f * N ** 3 / (6.0 * np.pi * mu * R * umean)
49
50
52 """2x nearest upsample x4 (self-similar packing: u_2N(2x) ~ 4*u_N(x)) -> near-steady seed."""
53 return np.repeat(np.repeat(np.repeat(a, 2, 0), 2, 1), 2, 2) * 4.0
54
55
56def run_sdflow(N, phi, mu=0.1, f=1e-3, dt=None, max_steps=600, tol=1e-6, seed=None):
57 """Multilevel MG-PCG pressure solve (rediscretized per-level cut-cell coarse operators).
58 `seed` = (u,v,w) from the next-coarser N, upsampled here, to start the march near steady."""
59 if dt is None:
60 dt = 60.0 if N <= 64 else 120.0
61 lv = max(2, int(np.log2(N)) - 1) # coarsen to ~4^3
62 sdf, R = sc_sdf_xyz(N, phi)
63 s = sdflow.Solver(N, N, N)
64 s.set_rho(1.0); s.set_mu(mu); s.set_dt(dt); s.set_body_force((f, 0, 0)); s.set_advection(False)
65 s.diagnostics.set_velocity_solver_params(200) # velocity RB-GS (the velocity MG under-
66 # converges the IBM diffusion -- separate bug)
67 s.set_pressure_multigrid(True, levels=lv)
68 s.set_pressure_pcg(True, max_iter=200, rtol=1e-8)
69 s.set_solid(sdf, cutcell_pressure=True)
70 if seed is not None:
71 u, v, w = (np.asfortranarray(_upsample2(c)) for c in seed)
72 s.set_state(u, v, w)
73 prev = 0.0; t0 = time.time()
74 for it in range(max_steps):
75 s.step()
76 if it % 5 == 4:
77 m = float(s.get_u().mean())
78 if it > 10 and abs(m - prev) < tol * (abs(m) + 1e-30):
79 break
80 prev = m
81 m = float(s.get_u().mean())
82 return drag_K(m, R, N, f, mu), (s.get_u(), s.get_v(), s.get_w()), time.time() - t0
83
84
85def main():
86 Ns = [int(x) for x in sys.argv[1].split(",")] if len(sys.argv) > 1 else [32, 64, 128]
87 phis = [float(x) for x in sys.argv[2].split(",")] if len(sys.argv) > 2 else [0.064, 0.125, 0.216]
88 print("=== Zick & Homsy (1982) SC drag factor K: sdflow ===")
89 print(f"{'phi':>7} {'N':>4} {'K_ZH':>8} {'K_sdflow':>9} {'err%':>7}")
90 for phi in phis:
91 kref = zh_ref(phi)
92 seed = None # coarse->fine continuation per phi
93 for N in sorted(Ns):
94 ks, fld, ts = run_sdflow(N, phi, seed=seed)
95 seed = fld
96 print(f"{phi:7.4f} {N:4d} {kref:8.3f} {ks:9.3f} {100*(ks-kref)/kref:7.2f}", flush=True)
97
98
99if __name__ == "__main__":
100 main()
run_sdflow(N, phi, mu=0.1, f=1e-3, dt=None, max_steps=600, tol=1e-6, seed=None)
static double mean(const std::vector< double > &a)