pnm 1.0.3
peclet.pnm — pore-network extraction from SDF geometry (Kokkos)
Loading...
Searching...
No Matches
demo_network_flow_packing.py
Go to the documentation of this file.
1"""Network-flow extraction on a real sphere packing: DNS (peclet.flow Stokes) -> pore network.
2
3Reads a packing SDF (VTI), solves body-force-driven Stokes flow through it, then extracts the
4pore network with throat flow rates and pore pressures (pnm.extract_network_flow) and reports
5network statistics + the built-in mass-balance check.
6
7Run: PYTHONPATH=<pnm/build>:<flow/build> python scripts/demo_network_flow_packing.py [sdf.vti]
8"""
9import sys
10import time
11
12import numpy as np
13
14import peclet.flow
15import peclet.pnm as pnm
16
17vti = sys.argv[1] if len(sys.argv) > 1 else "../flow/data/packing_128.vti"
18sdf_zyx, origin_zyx, spacing_zyx = pnm.SDFReader.read_vti(vti)
19nz, ny, nx = sdf_zyx.shape
20print(f"{vti}: grid {nx}x{ny}x{nz}, spacing {spacing_zyx[::-1]}, "
21 f"porosity {(sdf_zyx > 0).mean():.3f}")
22
23# ---- DNS: Stokes, body force in x, periodic --------------------------------------------------
24MU, FX = 0.05, 1.0e-3
25sdf_xyz = np.asfortranarray(sdf_zyx.T.astype(np.float64))
26s = peclet.flow.Solver(nx, ny, nz)
27s.set_rho(1.0)
28s.set_mu(MU)
29s.set_dt(100.0)
30s.set_body_force((FX, 0.0, 0.0)) # one 3-sequence since 1.0.0 (the clean break)
31s.set_solid(sdf_xyz, cutcell_pressure=True)
32t0 = time.time()
33for _ in range(40):
34 s.step()
35u, v, w, p = s.get_uf(), s.get_vf(), s.get_wf(), s.get_p()
36ox, oy, oz = s.get_ox(), s.get_oy(), s.get_oz()
37F = np.array([(ox[i] * u[i]).sum() for i in range(nx)]) * spacing_zyx[1] * spacing_zyx[0]
38print(f"DNS: {time.time()-t0:.1f} s, flux F = {F.mean():.4e} "
39 f"(plane dev {np.abs(F-F.mean()).max()/abs(F.mean()):.1e})")
40
41# ---- network extraction -----------------------------------------------------------------------
42t0 = time.time()
43net = pnm.extract_network_flow(
44 sdf_zyx, list(origin_zyx), list(spacing_zyx),
45 np.ascontiguousarray(u.T), np.ascontiguousarray(v.T), np.ascontiguousarray(w.T),
46 np.ascontiguousarray(p.T), np.ascontiguousarray(ox.T), np.ascontiguousarray(oy.T),
47 np.ascontiguousarray(oz.T), grad_p_zyx=[0.0, 0.0, -FX])
48Q = np.array(net["throat_flow"])
49dp = np.array(net["throat_dp"])
50A = np.array(net["throat_area"])
51res = np.array(net["pore_residual"])
52g = Q / dp
53print(f"extraction: {time.time()-t0:.1f} s")
54print(f"network: {len(net['pores'])} pores, {len(Q)} throats")
55print(f"mass residuals: max |res| = {np.abs(res).max():.3e} "
56 f"({np.abs(res).max()/np.abs(Q).mean():.2e} of mean |Q|)")
57print(f"throat |Q|: mean {np.abs(Q).mean():.3e} max {np.abs(Q).max():.3e}")
58print(f"throat dp: mean |dp| {np.abs(dp).mean():.3e}")
59print(f"g = Q/dp > 0 on {100.0*(g > 0).mean():.1f}% of throats")
60print(" NOTE: on loose packings (porosity ~0.6) the intra-pore viscous pressure variation is\n"
61 " comparable to the throat drops, so point-sampled pore dp does not resolve individual\n"
62 " throat resistances (g scatters; the Voronoi-PNM workflow fitted geometric conductances\n"
63 " for the same reason). On constricted geometry g>0 holds throat-by-throat — see\n"
64 " scripts/verify_network_flow.py (chamber-tube chain + asymmetric tube lattice).")
65
66ok = np.abs(res).max() < 1e-6 * np.abs(Q).max() and np.isfinite(g).all()
67print("PASS" if ok else "FAIL")
68del s
69sys.exit(0 if ok else 1)