flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
tbf_profiles.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Convert one TBFsolver output folder's statistics into the same NPZ `run_channel_18.py` writes.
3
4 python tbf_profiles.py <TBFsolver output folder> [--ny 160] [--out tbf_profiles.npz]
5
6TBFsolver's statistics (`src/statistics/statistics.f90`) are TIME averages of PLANE integrals,
7divided by the slice volume — so for the region `R` (`_W` whole, `_G` gas, `_L` liquid, with the
8weight `cv` = 1, `c`, `1 − c` and `c` the GAS fraction, the same convention as our transcription):
9
10 stats_c <c>(y) 1 column
11 stats_u<R> <cv ux>, <cv uy>, <cv uz>, 9 columns
12 <cv uxux>, <cv uxuy>, <cv uxuz>,
13 <cv uyuy>, <cv uyuz>, <cv uzuz>
14 tauw the time-averaged wall shear stress
15
16They are NOT conditioned averages: the liquid mean velocity is `<(1−c)u>/<1−c>`, which is what this
17script forms, exactly as `run_channel_18.py` does with its own `L`-weighted sums. Both codes'
18NPZ therefore carry the same quantities under the same names and the plotting script can overlay
19them without further arithmetic.
20"""
21import os
22import sys
23
24import numpy as np
25
26MU = 3.33333333333333e-4
27RET = 127.3
28UTAU = RET * MU
29LY = 2.0
30
31
32def arg(name, default):
33 return sys.argv[sys.argv.index(name) + 1] if name in sys.argv else default
34
35
36def col(path, ncol):
37 a = np.loadtxt(path)
38 a = a.reshape(-1, ncol) if a.ndim == 1 else a
39 return a
40
41
42def main():
43 d = sys.argv[1]
44 ny = int(arg("--ny", 160))
45 out = arg("--out", "tbf_profiles.npz")
46 y = (np.arange(ny) + 0.5) * LY / ny
47
48 c = col(os.path.join(d, "stats_c"), 1)[:, 0]
49 uW = col(os.path.join(d, "stats_u_W"), 9)
50 uL = col(os.path.join(d, "stats_u_L"), 9)
51
52 liq = np.maximum(1.0 - c, 1e-30)
53 r = {}
54 r["y"] = y
55 r["yplus"] = y / (MU / UTAU)
56 r["alpha"] = c
57 r["liquid_fraction"] = 1.0 - c
58 r["u"], r["v"], r["w"] = uW[:, 0], uW[:, 1], uW[:, 2]
59 r["uu"], r["uv"] = uW[:, 3], uW[:, 4]
60 r["urms"] = np.sqrt(np.maximum(uW[:, 3] - uW[:, 0] ** 2, 0.0))
61 r["vrms"] = np.sqrt(np.maximum(uW[:, 6] - uW[:, 1] ** 2, 0.0))
62 r["wrms"] = np.sqrt(np.maximum(uW[:, 8] - uW[:, 2] ** 2, 0.0))
63 ul, vl, wl = uL[:, 0] / liq, uL[:, 1] / liq, uL[:, 2] / liq
64 r["u_liq"], r["v_liq"], r["w_liq"] = ul, vl, wl
65 r["urms_liq"] = np.sqrt(np.maximum(uL[:, 3] / liq - ul ** 2, 0.0))
66 r["vrms_liq"] = np.sqrt(np.maximum(uL[:, 6] / liq - vl ** 2, 0.0))
67 r["wrms_liq"] = np.sqrt(np.maximum(uL[:, 8] / liq - wl ** 2, 0.0))
68 r["uv_liq"] = uL[:, 4] / liq - ul * vl
69 dy = LY / ny
70 r["utau_lo"] = float(np.sqrt(MU * abs(ul[0]) / (0.5 * dy)))
71 r["utau_hi"] = float(np.sqrt(MU * abs(ul[-1]) / (0.5 * dy)))
72 r["utau_wall"] = 0.5 * (r["utau_lo"] + r["utau_hi"])
73 r["utau"] = UTAU
74 r["utau_imposed"] = UTAU
75 tw = os.path.join(d, "tauw")
76 if os.path.exists(tw):
77 r["tauw_tav"] = float(np.loadtxt(tw))
78 r["grid"] = np.asarray([192, ny, 96])
79 np.savez(out, **{k: np.asarray(v) for k, v in r.items()})
80 print(f"wrote {out} from {d}")
81 print(f" <alpha> centreline {c[ny//2]:.5f}, bulk {c.mean():.5f}")
82 print(f" <u>_liq/u_tau peak {np.max(ul)/UTAU:.3f} at y/h = {y[int(np.argmax(ul))]:.3f}, "
83 f"centreline {ul[ny//2]/UTAU:.3f}")
84 print(f" u_tau wall gradient {r['utau_wall']:.6f} vs imposed {UTAU:.6f} "
85 f"({100*(r['utau_wall']/UTAU-1):+.1f} %)")
86
87
88if __name__ == "__main__":
89 main()
arg(name, default)
col(path, ncol)