flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
pocket_mask_probe.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Option (iv) of the pocket study: what changes if every staggered velocity point whose
3PROJECTION aperture is exactly 0 is masked (pinned to zero), so that the momentum mask agrees with
4the projection? A study instrument, not a gate.
5
6The masking itself lives in a STUDY-ONLY build: src/flow_ibm_geometry.hpp, setSolidDevice, under
7`#ifdef PECLET_FLOW_STUDY_MASK_CLOSED`, never committed to production code. The patch is
8tests/study/pocket_mask_closed.patch: `git apply` it in a scratch worktree and configure a separate
9tree with -DCMAKE_CXX_FLAGS=-DPECLET_FLOW_STUDY_MASK_CLOSED (it prints the number of points masked). This script runs one case on whichever module is on PYTHONPATH
10and saves the outputs; `compare` diffs two such files (baseline build vs masked build).
11
12 run --case bed : the A1 bed as a scene (pocket_pressure_probe.set_bed_scene), configured
13 as steady_acceleration_gates.Case.configure, marched to steady.
14 --case sphere: one sphere of volume fraction --phi in the periodic unit cube, the
15 tests/python/test_hydro_force_units.py setup ('unit' system, N = 24,
16 nu dt / h^2 = 6), centre on a cell vertex shifted by --shift cells.
17 compare a.npz b.npz : |dK/K|, per-body reaction F / T change (relative to max |F| / |T|),
18 max |du| / max |u| away from the points masked in either run, the
19 velocities the mask removed, and the step counts.
20
21 PYTHONPATH=build_mask python tests/study/pocket_mask_probe.py run --case bed \\
22 --arrangement bed.npz --settings tight --out mask_bed_tight.npz
23"""
24import argparse
25import math
26import os
27import sys
28
29import numpy as np
30
31HERE = os.path.dirname(os.path.abspath(__file__))
32sys.path.insert(0, HERE)
33import pocket_pressure_probe as PP # noqa: E402
34import steady_acceleration_gates as SAG # noqa: E402
35
36
37def build_sphere(flow, a, st):
38 n, L = a.N, 1.0
39 s = flow.Solver((n, n, n), extent=(L, L, L))
40 h = L / n
41 s.set_rho(1.0)
42 s.set_mu(1.0)
43 s.set_dt(a.beta * h * h)
44 s.set_body_force((1.0, 0.0, 0.0))
45 s.set_advection(False)
46 s.diagnostics.set_velocity_solver_params(200)
47 s.set_pressure_multigrid(True, levels=max(2, int(np.log2(n)) - 1))
48 s.set_pressure_pcg(True, max_iter=st["pcg"][0], rtol=st["pcg"][1])
49 if st["vel_rtol"] is not None:
50 s.set_velocity_residual_tolerance(st["vel_rtol"])
51 R = (3.0 * a.phi / (4.0 * math.pi)) ** (1.0 / 3.0) * L
52 c = [0.5 * L + a.shift[k] * h for k in range(3)]
53 ni = np.array([1, -1, -1], dtype=np.int32)
54 nr = np.zeros(16)
55 nr[0] = R
56 nr[14] = 1.0
57 nr[15] = 1.0
58 ii = np.array([0, -1], dtype=np.int32)
59 ir = np.zeros(17)
60 ir[0:3] = c
61 ir[6] = 1.0
62 ir[7] = 1.0
63 s.set_scene(ni, nr, ii, ir, periodic=True)
64 s.set_solid_from_scene(cutcell_pressure=True)
65
66 def K(u): # Zick & Homsy normalisation
67 return L ** 3 / (6.0 * math.pi * R * u)
68 return s, K
69
70
71def cmd_run(flow, a):
72 st = SAG.settings_of(a.settings)
73 if a.max_steps:
74 st["max_steps"] = a.max_steps
75 if a.case == "bed":
76 case = SAG.Case(PP.case_args(a))
77 s = PP.build(flow, case, "staggered", st)
78 K = case.K
79 else:
80 s, K = build_sphere(flow, a, st)
81 res = flow.march_to_steady(s, lambda: float(s.get_u().mean()), rtol=st["rtol"],
82 max_steps=st["max_steps"], accelerate=(a.window > 0),
83 window=max(a.window, 1))
84 r = np.array(s.hydro_force_torque_reaction())
85 U = [np.array(g()) for g in (s.get_u, s.get_v, s.get_w)]
86 O = [np.array(g()) for g in (s.get_ox_proj, s.get_oy_proj, s.get_oz_proj)]
87 print(f"{a.case} {a.settings} m={a.window}: converged={res.converged} {res.reason} "
88 f"steps={res.steps} acc={res.accelerated_steps} K={K(res.monitor):.12f} "
89 f"sum Fx={r[0][:, 0].sum():.12e}", flush=True)
90 np.savez(a.out, K=K(res.monitor), monitor=res.monitor, steps=res.steps,
91 accelerated=res.accelerated_steps, converged=res.converged, F=r[0], T=r[1],
92 u=U[0], v=U[1], w=U[2], ox=O[0], oy=O[1], oz=O[2])
93
94
96 A, B = np.load(a.a), np.load(a.b)
97 dK = float(B["K"] / A["K"] - 1.0)
98 dF = float(np.abs(B["F"] - A["F"]).max() / np.abs(A["F"]).max())
99 dT = float(np.abs(B["T"] - A["T"]).max() / np.abs(A["T"]).max())
100 umax = max(float(np.abs(A[f]).max()) for f in "uvw")
101 du, rem, nmask = 0.0, 0.0, 0
102 for f, o in zip("uvw", ("ox", "oy", "oz")):
103 # a point either run pinned to zero (solid, or closed in the masked build)
104 pinned = (A[f] == 0.0) | (B[f] == 0.0)
105 closed = (A[o] == 0.0) & (A[f] != 0.0) # live in A although its aperture is 0
106 nmask += int(closed.sum())
107 rem = max(rem, float(np.abs(A[f][closed]).max()) if closed.any() else 0.0)
108 du = max(du, float(np.abs(B[f] - A[f])[~pinned].max()))
109 print(f"{os.path.basename(a.a)} -> {os.path.basename(a.b)}: |dK/K| {abs(dK):.3e} "
110 f"(K {float(A['K']):.12f} -> {float(B['K']):.12f}); reaction F {dF:.3e}, T {dT:.3e} "
111 f"(per body, / max); max|du| / max|u| off the pinned points {du / umax:.3e}; "
112 f"{nmask} closed-aperture points live in the first run, max |u| there / max|u| "
113 f"{rem / umax:.3e}; steps {int(A['steps'])} -> {int(B['steps'])} "
114 f"(accelerated {int(A['accelerated'])} -> {int(B['accelerated'])}), converged "
115 f"{bool(A['converged'])} -> {bool(B['converged'])}")
116
117
118def main():
119 ap = argparse.ArgumentParser()
120 ap.add_argument("cmd", choices=("run", "compare"))
121 ap.add_argument("a", nargs="?")
122 ap.add_argument("b", nargs="?")
123 ap.add_argument("--case", choices=("bed", "sphere"), default="bed")
124 ap.add_argument("--arrangement", default=None)
125 ap.add_argument("--N", type=int, default=None)
126 ap.add_argument("--phi", type=float, default=0.216)
127 ap.add_argument("--shift", type=float, nargs=3, default=[0.0, 0.0, 0.0])
128 ap.add_argument("--beta", type=float, default=6.0)
129 ap.add_argument("--settings", choices=("production", "tight"), default="production")
130 ap.add_argument("--window", type=int, default=5)
131 ap.add_argument("--max-steps", type=int, default=None)
132 ap.add_argument("--out", default=None)
133 a = ap.parse_args()
134 if a.cmd == "compare":
135 cmd_compare(a)
136 return
137 if a.N is None:
138 a.N = 64 if a.case == "bed" else 24
139 sys.path.insert(0, os.path.join(HERE, "..", "..", "scripts"))
140 from _bootstrap import ensure_flow
141 cmd_run(ensure_flow(), a)
142
143
144if __name__ == "__main__":
145 main()
build_sphere(flow, a, st)
static double mean(const std::vector< double > &a)