- Introduced new functions for computing and plotting TRT-LES fields. - Enhanced case diagnostics to include mass drift and inlet variance metrics. - Updated configuration to support inlet profile selection and TRT magic parameter. - Modified existing functions to accommodate new diagnostic calculations. - Improved case tagging to include inlet profile and TRT parameters. - Added checks for fluid dynamics and diagnostics in the run_case function.
986 lines
37 KiB
Python
986 lines
37 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
High-Re Validation (kernel_v2)
|
|
==============================
|
|
Unified validation script for high-Re runs with optional LES.
|
|
|
|
Default targets:
|
|
- 2D D2Q9: Re=5000
|
|
- 3D D3Q19: Re=3000
|
|
|
|
The script configures macros.h temporarily, compiles kernel_v2, runs the case,
|
|
and restores macros.h automatically.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import struct
|
|
import sys
|
|
import time
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src"))
|
|
|
|
import matplotlib
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
import pycuda.driver as cuda
|
|
|
|
from CelerisLab.cuda import compiler
|
|
|
|
FLUID_FLAG = 0x01
|
|
SOLID_FLAG = 0x02
|
|
OBSTACLE_FLAG = 0x04
|
|
|
|
OMEGA_COLLISION_MIN_DEFAULT = 0.01
|
|
LES_POST_FP_ITERS = 3
|
|
LES_POST_FP_RELAX = 0.70
|
|
LES_POST_NUT_MAX_RATIO = 20.0
|
|
|
|
|
|
def collision_name(model):
|
|
return {0: "SRT", 1: "TRT", 2: "MRT"}.get(model, f"M{model}")
|
|
|
|
|
|
def make_case_tag(cfg):
|
|
les_tag = "LES" if cfg["use_les"] else "NoLES"
|
|
inlet_tag = f"IP{int(cfg.get('inlet_profile', 0))}"
|
|
lam_tag = f"LAM{float(cfg.get('trt_magic_param', 0.1875)):.4f}"
|
|
return (
|
|
f"{cfg['name']}_Re{int(cfg['target_re'])}_"
|
|
f"{collision_name(cfg['collision_model'])}_{les_tag}_"
|
|
f"OM{int(cfg['outlet_mode'])}_{inlet_tag}_{lam_tag}_"
|
|
f"WMAX{cfg['omega_collision_max']:.3f}"
|
|
)
|
|
|
|
|
|
def validate_case(rho):
|
|
nan_count = int(np.isnan(rho).sum())
|
|
if nan_count > 0:
|
|
return False, "NaN detected"
|
|
|
|
rho_min = float(np.min(rho))
|
|
rho_max = float(np.max(rho))
|
|
if rho_min <= 0.0:
|
|
return False, "Non-positive density"
|
|
if rho_max >= 2.0:
|
|
return False, "Density blow-up"
|
|
return True, "OK"
|
|
|
|
|
|
def lattice_weights(nq):
|
|
if nq == 9:
|
|
return np.array(
|
|
[4.0 / 9.0] + [1.0 / 9.0] * 4 + [1.0 / 36.0] * 4,
|
|
dtype=np.float32,
|
|
)
|
|
if nq == 19:
|
|
return np.array(
|
|
[1.0 / 3.0] + [1.0 / 18.0] * 6 + [1.0 / 36.0] * 12,
|
|
dtype=np.float32,
|
|
)
|
|
raise ValueError(f"Unsupported nq={nq}")
|
|
|
|
|
|
def impose_rest_state_on_nonfluid(cfg, host_ddf):
|
|
nq = cfg["nq"]
|
|
nx, ny, nz = cfg["nx"], cfg["ny"], cfg["nz"]
|
|
w = lattice_weights(nq)
|
|
|
|
if nq == 9:
|
|
f = host_ddf.reshape(nq, ny, nx)
|
|
nonfluid = cfg["flag"].reshape(ny, nx) != FLUID_FLAG
|
|
for i in range(nq):
|
|
f[i, nonfluid] = w[i]
|
|
return host_ddf
|
|
|
|
f = host_ddf.reshape(nq, nz, ny, nx)
|
|
nonfluid = cfg["flag"].reshape(nz, ny, nx) != FLUID_FLAG
|
|
for i in range(nq):
|
|
f[i, nonfluid] = w[i]
|
|
return host_ddf
|
|
|
|
|
|
def compute_case_diagnostics(cfg, host_ddf):
|
|
nq = cfg["nq"]
|
|
nx, ny, nz = cfg["nx"], cfg["ny"], cfg["nz"]
|
|
|
|
if nq == 9:
|
|
f = host_ddf.reshape(nq, ny, nx)
|
|
rho = np.sum(f, axis=0)
|
|
ux = np.zeros_like(rho)
|
|
uy = np.zeros_like(rho)
|
|
cx = [0, 1, -1, 0, 0, 1, -1, 1, -1]
|
|
cy = [0, 0, 0, 1, -1, 1, -1, -1, 1]
|
|
for i in range(nq):
|
|
ux += cx[i] * f[i]
|
|
uy += cy[i] * f[i]
|
|
rho_safe = np.where(np.abs(rho) > 1.0e-12, rho, 1.0)
|
|
ux /= rho_safe
|
|
uy /= rho_safe
|
|
vel = np.sqrt(ux * ux + uy * uy)
|
|
|
|
fluid = cfg["flag"].reshape(ny, nx) == FLUID_FLAG
|
|
mass = float(np.nansum(rho[fluid]))
|
|
|
|
x0 = 1
|
|
x1 = min(nx - 1, 33)
|
|
inlet_window = np.zeros_like(fluid)
|
|
inlet_window[1:ny - 1, x0:x1] = True
|
|
win_mask = fluid & inlet_window
|
|
inlet_var = float(np.nanvar(vel[win_mask])) if np.any(win_mask) else float("nan")
|
|
|
|
# Quantify how well the first interior column follows the designed inlet profile.
|
|
x_probe = 1
|
|
line_mask = fluid[:, x_probe]
|
|
line_u = ux[:, x_probe]
|
|
y = np.arange(ny, dtype=np.float32)
|
|
if int(cfg.get("inlet_profile", 0)) == 0:
|
|
target = np.full(ny, float(cfg["u0"]), dtype=np.float32)
|
|
else:
|
|
yy = (y - 0.5 * (ny - 1)) / (ny - 2.0)
|
|
target = float(cfg["u0"]) * 1.5 * (1.0 - 4.0 * yy * yy)
|
|
|
|
if np.any(line_mask):
|
|
diff = line_u[line_mask] - target[line_mask]
|
|
t_ref = float(np.max(np.abs(target[line_mask])))
|
|
denom = max(t_ref, 1.0e-8)
|
|
inlet_rel_l2 = float(np.sqrt(np.mean(diff * diff)) / denom)
|
|
inlet_rel_linf = float(np.max(np.abs(diff)) / denom)
|
|
else:
|
|
inlet_rel_l2 = float("nan")
|
|
inlet_rel_linf = float("nan")
|
|
|
|
# Inlet-plane-wave indicator: streamwise oscillation of column-averaged
|
|
# macros in the pre-obstacle region.
|
|
cx = float(cfg.get("cx", 0.25 * nx))
|
|
radius = float(cfg.get("radius", ny / 12.0))
|
|
x_pre0 = 1
|
|
x_pre1 = min(nx - 2, max(x_pre0 + 4, int(cx - 1.5 * radius)))
|
|
col_u = []
|
|
col_r = []
|
|
for xp in range(x_pre0, x_pre1):
|
|
col_mask = fluid[1:ny - 1, xp]
|
|
if np.any(col_mask):
|
|
col_u.append(float(np.mean(ux[1:ny - 1, xp][col_mask])))
|
|
col_r.append(float(np.mean(rho[1:ny - 1, xp][col_mask])))
|
|
|
|
if int(cfg.get("inlet_profile", 0)) == 0:
|
|
u_target_mean = float(cfg["u0"])
|
|
else:
|
|
y_int = np.arange(1, ny - 1, dtype=np.float32)
|
|
yy_int = (y_int - 0.5 * (ny - 1)) / (ny - 2.0)
|
|
target_int = float(cfg["u0"]) * 1.5 * (1.0 - 4.0 * yy_int * yy_int)
|
|
u_target_mean = float(np.mean(target_int)) if target_int.size > 0 else float(cfg["u0"])
|
|
|
|
if len(col_u) >= 4:
|
|
col_u_arr = np.array(col_u, dtype=np.float64)
|
|
col_r_arr = np.array(col_r, dtype=np.float64)
|
|
inlet_wave_ux_rel = float(np.std(col_u_arr - u_target_mean) / max(abs(u_target_mean), 1.0e-8))
|
|
rho_ref = max(abs(float(np.mean(col_r_arr))), 1.0e-8)
|
|
inlet_wave_rho_rel = float(np.std(col_r_arr) / rho_ref)
|
|
else:
|
|
inlet_wave_ux_rel = float("nan")
|
|
inlet_wave_rho_rel = float("nan")
|
|
|
|
# TRT checker/grid-noise indicator: odd-even imbalance in wake ux field.
|
|
xw0 = min(nx - 3, max(2, int(cx + 2.0 * radius)))
|
|
xw1 = min(nx - 2, max(xw0 + 4, int(cx + 12.0 * radius)))
|
|
yw0, yw1 = 2, ny - 2
|
|
if xw1 > xw0 + 2 and yw1 > yw0 + 2:
|
|
reg = ux[yw0:yw1, xw0:xw1].astype(np.float64)
|
|
reg_mask = fluid[yw0:yw1, xw0:xw1]
|
|
if np.any(reg_mask):
|
|
valid = reg[reg_mask]
|
|
m = float(np.mean(valid))
|
|
centered = np.where(reg_mask, reg - m, np.nan)
|
|
rms = float(np.sqrt(np.mean((valid - m) * (valid - m))))
|
|
yy_i, xx_i = np.indices(centered.shape)
|
|
even_vals = centered[((xx_i + yy_i) & 1) == 0]
|
|
odd_vals = centered[((xx_i + yy_i) & 1) == 1]
|
|
even_vals = even_vals[np.isfinite(even_vals)]
|
|
odd_vals = odd_vals[np.isfinite(odd_vals)]
|
|
|
|
if rms > 1.0e-12 and even_vals.size > 8 and odd_vals.size > 8:
|
|
wake_checker_rel = float(abs(np.mean(even_vals) - np.mean(odd_vals)) / rms)
|
|
else:
|
|
wake_checker_rel = float("nan")
|
|
|
|
pair_mask = reg_mask[:, :-1] & reg_mask[:, 1:]
|
|
a = centered[:, :-1][pair_mask]
|
|
b = centered[:, 1:][pair_mask]
|
|
if a.size > 16 and np.std(a) > 1.0e-12 and np.std(b) > 1.0e-12:
|
|
corr = float(np.corrcoef(a, b)[0, 1])
|
|
wake_checker_anti_corr_x = float(max(0.0, -corr))
|
|
else:
|
|
wake_checker_anti_corr_x = float("nan")
|
|
else:
|
|
wake_checker_rel = float("nan")
|
|
wake_checker_anti_corr_x = float("nan")
|
|
else:
|
|
wake_checker_rel = float("nan")
|
|
wake_checker_anti_corr_x = float("nan")
|
|
|
|
return {
|
|
"mass": mass,
|
|
"inlet_var": inlet_var,
|
|
"inlet_line_rel_l2": inlet_rel_l2,
|
|
"inlet_line_rel_linf": inlet_rel_linf,
|
|
"inlet_wave_ux_rel": inlet_wave_ux_rel,
|
|
"inlet_wave_rho_rel": inlet_wave_rho_rel,
|
|
"wake_checker_rel": wake_checker_rel,
|
|
"wake_checker_anti_corr_x": wake_checker_anti_corr_x,
|
|
}
|
|
|
|
f = host_ddf.reshape(nq, nz, ny, nx)
|
|
rho = np.sum(f, axis=0)
|
|
ux = np.zeros_like(rho)
|
|
uy = np.zeros_like(rho)
|
|
uz = np.zeros_like(rho)
|
|
cx = np.array([0, 1,-1, 0, 0, 0, 0, 1,-1, 1,-1, 0, 0, 1,-1, 1,-1, 0, 0])
|
|
cy = np.array([0, 0, 0, 1,-1, 0, 0, 1,-1, 0, 0, 1,-1,-1, 1, 0, 0, 1,-1])
|
|
cz = np.array([0, 0, 0, 0, 0, 1,-1, 0, 0, 1,-1, 1,-1, 0, 0,-1, 1,-1, 1])
|
|
for i in range(nq):
|
|
ux += cx[i] * f[i]
|
|
uy += cy[i] * f[i]
|
|
uz += cz[i] * f[i]
|
|
rho_safe = np.where(np.abs(rho) > 1.0e-12, rho, 1.0)
|
|
ux /= rho_safe
|
|
uy /= rho_safe
|
|
uz /= rho_safe
|
|
vel = np.sqrt(ux * ux + uy * uy + uz * uz)
|
|
|
|
fluid = cfg["flag"].reshape(nz, ny, nx) == FLUID_FLAG
|
|
mass = float(np.nansum(rho[fluid]))
|
|
|
|
x0 = 1
|
|
x1 = min(nx - 1, 17)
|
|
inlet_window = np.zeros_like(fluid)
|
|
inlet_window[:, 1:ny - 1, x0:x1] = True
|
|
win_mask = fluid & inlet_window
|
|
inlet_var = float(np.nanvar(vel[win_mask])) if np.any(win_mask) else float("nan")
|
|
|
|
cx = float(cfg.get("cx", 0.25 * nx))
|
|
radius = float(cfg.get("radius", ny / 12.0))
|
|
x_pre0 = 1
|
|
x_pre1 = min(nx - 2, max(x_pre0 + 4, int(cx - 1.5 * radius)))
|
|
col_u = []
|
|
col_r = []
|
|
for xp in range(x_pre0, x_pre1):
|
|
col_mask = fluid[:, 1:ny - 1, xp]
|
|
if np.any(col_mask):
|
|
col_u.append(float(np.mean(ux[:, 1:ny - 1, xp][col_mask])))
|
|
col_r.append(float(np.mean(rho[:, 1:ny - 1, xp][col_mask])))
|
|
|
|
if int(cfg.get("inlet_profile", 0)) == 0:
|
|
u_target_mean = float(cfg["u0"])
|
|
else:
|
|
y_int = np.arange(1, ny - 1, dtype=np.float32)
|
|
yy_int = (y_int - 0.5 * (ny - 1)) / (ny - 2.0)
|
|
target_int = float(cfg["u0"]) * 1.5 * (1.0 - 4.0 * yy_int * yy_int)
|
|
u_target_mean = float(np.mean(target_int)) if target_int.size > 0 else float(cfg["u0"])
|
|
|
|
if len(col_u) >= 4:
|
|
col_u_arr = np.array(col_u, dtype=np.float64)
|
|
col_r_arr = np.array(col_r, dtype=np.float64)
|
|
inlet_wave_ux_rel = float(np.std(col_u_arr - u_target_mean) / max(abs(u_target_mean), 1.0e-8))
|
|
rho_ref = max(abs(float(np.mean(col_r_arr))), 1.0e-8)
|
|
inlet_wave_rho_rel = float(np.std(col_r_arr) / rho_ref)
|
|
else:
|
|
inlet_wave_ux_rel = float("nan")
|
|
inlet_wave_rho_rel = float("nan")
|
|
|
|
return {
|
|
"mass": mass,
|
|
"inlet_var": inlet_var,
|
|
"inlet_wave_ux_rel": inlet_wave_ux_rel,
|
|
"inlet_wave_rho_rel": inlet_wave_rho_rel,
|
|
"wake_checker_rel": float("nan"),
|
|
"wake_checker_anti_corr_x": float("nan"),
|
|
"inlet_line_rel_l2": float("nan"),
|
|
"inlet_line_rel_linf": float("nan"),
|
|
}
|
|
|
|
|
|
def compute_trt_les_fields_2d(cfg, host_ddf):
|
|
if cfg["nq"] != 9:
|
|
return None
|
|
|
|
nx, ny = cfg["nx"], cfg["ny"]
|
|
f = host_ddf.reshape(9, ny, nx).astype(np.float64)
|
|
|
|
cx = np.array([0, 1, -1, 0, 0, 1, -1, 1, -1], dtype=np.float64).reshape(9, 1, 1)
|
|
cy = np.array([0, 0, 0, 1, -1, 1, -1, -1, 1], dtype=np.float64).reshape(9, 1, 1)
|
|
w = lattice_weights(9).astype(np.float64).reshape(9, 1, 1)
|
|
|
|
rho = np.sum(f, axis=0)
|
|
rho_safe = np.where(np.abs(rho) > 1.0e-12, rho, 1.0)
|
|
ux = np.sum(f * cx, axis=0) / rho_safe
|
|
uy = np.sum(f * cy, axis=0) / rho_safe
|
|
|
|
u2 = ux * ux + uy * uy
|
|
cu = 3.0 * (cx * ux[None, :, :] + cy * uy[None, :, :])
|
|
feq = w * rho[None, :, :] * (1.0 + cu + 0.5 * cu * cu - 1.5 * u2[None, :, :])
|
|
fneq = f - feq
|
|
|
|
pixx = np.sum(fneq * cx * cx, axis=0)
|
|
piyy = np.sum(fneq * cy * cy, axis=0)
|
|
pixy = np.sum(fneq * cx * cy, axis=0)
|
|
|
|
omega0 = float(cfg["omega"])
|
|
omega_min = float(cfg.get("omega_collision_min", OMEGA_COLLISION_MIN_DEFAULT))
|
|
omega_max = float(cfg["omega_collision_max"])
|
|
tau0 = max(1.0 / max(omega0, 1.0e-6), 0.500001)
|
|
nu0 = (tau0 - 0.5) * (1.0 / 3.0)
|
|
tau_max = 1.0 / max(omega_min, 1.0e-6)
|
|
|
|
tau_eff = np.full((ny, nx), tau0, dtype=np.float64)
|
|
nut = np.zeros((ny, nx), dtype=np.float64)
|
|
rho_ref = np.maximum(rho_safe, 1.0e-12)
|
|
cs2 = 1.0 / 3.0
|
|
nut_cap = max(0.0, LES_POST_NUT_MAX_RATIO * nu0)
|
|
cs = float(cfg.get("les_cs", 0.16))
|
|
|
|
for _ in range(LES_POST_FP_ITERS):
|
|
denom = 2.0 * rho_ref * cs2 * np.maximum(tau_eff, 0.500001)
|
|
sxx = -pixx / denom
|
|
syy = -piyy / denom
|
|
sxy = -pixy / denom
|
|
|
|
tr = 0.5 * (sxx + syy)
|
|
sxx_dev = sxx - tr
|
|
syy_dev = syy - tr
|
|
sxy_dev = sxy
|
|
s_mag = np.sqrt(np.maximum(0.0, 2.0 * (sxx_dev * sxx_dev + syy_dev * syy_dev + 2.0 * sxy_dev * sxy_dev)))
|
|
|
|
nut = np.clip((cs * cs) * s_mag, 0.0, nut_cap)
|
|
tau_new = np.clip(0.5 + 3.0 * (nu0 + nut), 0.500001, tau_max)
|
|
tau_eff = LES_POST_FP_RELAX * tau_new + (1.0 - LES_POST_FP_RELAX) * tau_eff
|
|
|
|
omega_plus = np.clip(1.0 / tau_eff, omega_min, omega_max)
|
|
denom_odd = np.maximum(1.0 / omega_plus - 0.5, 1.0e-9)
|
|
lam = float(cfg.get("trt_magic_param", 0.1875))
|
|
omega_minus = 1.0 / (lam / denom_odd + 0.5)
|
|
|
|
fluid = cfg["flag"].reshape(ny, nx) == FLUID_FLAG
|
|
rho_mean = float(np.mean(rho[fluid])) if np.any(fluid) else float(np.mean(rho))
|
|
rho_prime = rho - rho_mean
|
|
|
|
return {
|
|
"fluid": fluid,
|
|
"omega_plus": omega_plus,
|
|
"omega_minus": omega_minus,
|
|
"nut": nut,
|
|
"rho_prime": rho_prime,
|
|
}
|
|
|
|
|
|
def plot_trt_les_maps(cfg, host_ddf, out_dir):
|
|
if cfg["nq"] != 9 or cfg["collision_model"] != 1 or not cfg["use_les"]:
|
|
return None
|
|
|
|
fields = compute_trt_les_fields_2d(cfg, host_ddf)
|
|
if fields is None:
|
|
return None
|
|
|
|
fluid = fields["fluid"]
|
|
tag = make_case_tag(cfg)
|
|
out_path = os.path.join(out_dir, f"{tag}_trt_les_fields.png")
|
|
|
|
fig, axes = plt.subplots(2, 2, figsize=(12, 9))
|
|
panels = [
|
|
("omega_plus", r"$\omega_+$", "viridis", False),
|
|
("omega_minus", r"$\omega_-$", "viridis", False),
|
|
("nut", r"$\nu_t$", "magma", False),
|
|
("rho_prime", r"$\rho-\overline{\rho}$", "RdBu_r", True),
|
|
]
|
|
|
|
for ax, (key, title, cmap, signed) in zip(axes.ravel(), panels):
|
|
arr = fields[key]
|
|
arr_m = np.ma.array(arr, mask=~fluid)
|
|
finite_vals = arr[fluid]
|
|
if finite_vals.size == 0:
|
|
vmin, vmax = 0.0, 1.0
|
|
elif signed:
|
|
span = np.percentile(np.abs(finite_vals), 99)
|
|
span = max(float(span), 1.0e-12)
|
|
vmin, vmax = -span, span
|
|
else:
|
|
q1 = float(np.percentile(finite_vals, 1))
|
|
q99 = float(np.percentile(finite_vals, 99))
|
|
if not np.isfinite(q1) or not np.isfinite(q99) or q99 <= q1:
|
|
q1 = float(np.min(finite_vals))
|
|
q99 = float(np.max(finite_vals))
|
|
if q99 <= q1:
|
|
q99 = q1 + 1.0e-12
|
|
vmin, vmax = q1, q99
|
|
|
|
im = ax.imshow(arr_m, origin="lower", aspect="auto", cmap=cmap, vmin=vmin, vmax=vmax)
|
|
plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
|
|
ax.set_title(title)
|
|
ax.set_xlabel("x")
|
|
ax.set_ylabel("y")
|
|
|
|
fig.suptitle(f"{tag} TRT-LES diagnostics")
|
|
fig.tight_layout()
|
|
fig.savefig(out_path, dpi=160)
|
|
plt.close(fig)
|
|
return out_path
|
|
|
|
|
|
def plot_case(cfg, host_ddf, out_dir):
|
|
nq = cfg["nq"]
|
|
nx, ny, nz = cfg["nx"], cfg["ny"], cfg["nz"]
|
|
flag = cfg["flag"]
|
|
tag = make_case_tag(cfg)
|
|
out_path = os.path.join(out_dir, f"{tag}.png")
|
|
|
|
if nq == 9:
|
|
f = host_ddf.reshape(nq, ny, nx)
|
|
rho = np.sum(f, axis=0)
|
|
ux = np.zeros_like(rho)
|
|
uy = np.zeros_like(rho)
|
|
cx = [0, 1, -1, 0, 0, 1, -1, 1, -1]
|
|
cy = [0, 0, 0, 1, -1, 1, -1, -1, 1]
|
|
for i in range(nq):
|
|
ux += cx[i] * f[i]
|
|
uy += cy[i] * f[i]
|
|
rho_safe = np.where(np.abs(rho) > 1.0e-12, rho, 1.0)
|
|
ux /= rho_safe
|
|
uy /= rho_safe
|
|
vel = np.sqrt(ux * ux + uy * uy)
|
|
|
|
mask = flag.reshape(ny, nx) != FLUID_FLAG
|
|
vel_m = np.ma.array(vel, mask=mask)
|
|
vort = np.gradient(uy, axis=1) - np.gradient(ux, axis=0)
|
|
vort_m = np.ma.array(vort, mask=mask)
|
|
|
|
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
|
|
|
|
im0 = axes[0].imshow(vel_m, origin="lower", aspect="auto", cmap="turbo")
|
|
plt.colorbar(im0, ax=axes[0], label="|u|")
|
|
axes[0].set_title("Velocity Magnitude")
|
|
|
|
vmax = np.percentile(np.abs(vort[~mask]), 99) if np.any(~mask) else 1e-6
|
|
vmax = max(vmax, 1.0e-6)
|
|
im1 = axes[1].imshow(vort_m, origin="lower", aspect="auto", cmap="RdBu_r", vmin=-vmax, vmax=vmax)
|
|
plt.colorbar(im1, ax=axes[1], label="vorticity")
|
|
axes[1].set_title("Vorticity")
|
|
|
|
X, Y = np.meshgrid(np.arange(nx), np.arange(ny))
|
|
ux_s = np.ma.array(ux, mask=mask)
|
|
uy_s = np.ma.array(uy, mask=mask)
|
|
speed = np.ma.sqrt(ux_s * ux_s + uy_s * uy_s)
|
|
axes[2].streamplot(X, Y, ux_s, uy_s, color=speed, cmap="viridis", density=2.0, linewidth=0.7)
|
|
axes[2].set_xlim(0, nx)
|
|
axes[2].set_ylim(0, ny)
|
|
axes[2].set_title("Streamlines")
|
|
|
|
fig.suptitle(tag)
|
|
fig.tight_layout()
|
|
fig.savefig(out_path, dpi=150)
|
|
plt.close(fig)
|
|
return out_path
|
|
|
|
# D3Q19: visualize mid-z slice
|
|
f = host_ddf.reshape(nq, nz, ny, nx)
|
|
z0 = nz // 2
|
|
fs = f[:, z0, :, :]
|
|
rho = np.sum(fs, axis=0)
|
|
ux = np.zeros_like(rho)
|
|
uy = np.zeros_like(rho)
|
|
uz = np.zeros_like(rho)
|
|
cx = np.array([0, 1,-1, 0, 0, 0, 0, 1,-1, 1,-1, 0, 0, 1,-1, 1,-1, 0, 0])
|
|
cy = np.array([0, 0, 0, 1,-1, 0, 0, 1,-1, 0, 0, 1,-1,-1, 1, 0, 0, 1,-1])
|
|
cz = np.array([0, 0, 0, 0, 0, 1,-1, 0, 0, 1,-1, 1,-1, 0, 0,-1, 1,-1, 1])
|
|
for i in range(nq):
|
|
ux += cx[i] * fs[i]
|
|
uy += cy[i] * fs[i]
|
|
uz += cz[i] * fs[i]
|
|
rho_safe = np.where(np.abs(rho) > 1.0e-12, rho, 1.0)
|
|
ux /= rho_safe
|
|
uy /= rho_safe
|
|
uz /= rho_safe
|
|
vel = np.sqrt(ux * ux + uy * uy + uz * uz)
|
|
|
|
mask3 = flag.reshape(nz, ny, nx)[z0] != FLUID_FLAG
|
|
vel_m = np.ma.array(vel, mask=mask3)
|
|
vort = np.gradient(uy, axis=1) - np.gradient(ux, axis=0)
|
|
vort_m = np.ma.array(vort, mask=mask3)
|
|
|
|
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
|
|
im0 = axes[0].imshow(vel_m, origin="lower", aspect="auto", cmap="turbo")
|
|
plt.colorbar(im0, ax=axes[0], label="|u|")
|
|
axes[0].set_title("Velocity Magnitude (z-mid)")
|
|
|
|
vmax = np.percentile(np.abs(vort[~mask3]), 99) if np.any(~mask3) else 1.0e-6
|
|
vmax = max(vmax, 1.0e-6)
|
|
im1 = axes[1].imshow(vort_m, origin="lower", aspect="auto", cmap="RdBu_r", vmin=-vmax, vmax=vmax)
|
|
plt.colorbar(im1, ax=axes[1], label="vorticity")
|
|
axes[1].set_title("Vorticity (z-mid)")
|
|
|
|
X, Y = np.meshgrid(np.arange(nx), np.arange(ny))
|
|
ux_s = np.ma.array(ux, mask=mask3)
|
|
uy_s = np.ma.array(uy, mask=mask3)
|
|
speed = np.ma.sqrt(ux_s * ux_s + uy_s * uy_s)
|
|
axes[2].streamplot(X, Y, ux_s, uy_s, color=speed, cmap="viridis", density=2.0, linewidth=0.7)
|
|
axes[2].set_xlim(0, nx)
|
|
axes[2].set_ylim(0, ny)
|
|
axes[2].set_title("Streamlines (z-mid)")
|
|
|
|
fig.suptitle(tag)
|
|
fig.tight_layout()
|
|
fig.savefig(out_path, dpi=150)
|
|
plt.close(fig)
|
|
return out_path
|
|
|
|
|
|
def compute_vis_omega(reynolds, diameter, u0):
|
|
vis = u0 * diameter / reynolds
|
|
omega = 1.0 / (3.0 * vis + 0.5)
|
|
return vis, omega
|
|
|
|
|
|
def set_macros(nx, ny, nz, dim, nq, vis, u0, collision_model, use_les, les_cs,
|
|
outlet_mode, outlet_backflow_clamp, outlet_blend_alpha,
|
|
omega_collision_max, inlet_profile, trt_magic_param):
|
|
lines = compiler.read_lines(compiler.kernel_path("macros.h"))
|
|
defs = {
|
|
"MULT_GPU": "False",
|
|
"NT": 128,
|
|
"X_1U": nx,
|
|
"Y_1U": ny,
|
|
"Z_1U": nz,
|
|
"LBtype": "float",
|
|
"UX": 1,
|
|
"UY": 1,
|
|
"UZ": 1,
|
|
"NX": nx,
|
|
"NY": ny,
|
|
"NZ": nz,
|
|
"DIM": dim,
|
|
"NQ": nq,
|
|
"VIS": f"{vis:.10f}",
|
|
"RHO": "1.0",
|
|
"U0": u0,
|
|
"N_OBJS": 0,
|
|
"COLLISION_MODEL": collision_model,
|
|
"STREAMING_MODEL": 0,
|
|
"STORE_PRECISION": 0,
|
|
"USE_DDF_SHIFTING": 0,
|
|
"USE_LES": int(use_les),
|
|
"LES_CS": f"{les_cs:.6f}f",
|
|
"INLET_PROFILE": int(inlet_profile),
|
|
"OUTLET_MODE": int(outlet_mode),
|
|
"OUTLET_BACKFLOW_CLAMP": int(outlet_backflow_clamp),
|
|
"OUTLET_BLEND_ALPHA": f"{float(outlet_blend_alpha):.3f}f",
|
|
"OMEGA_COLLISION_MAX": f"{float(omega_collision_max):.3f}f",
|
|
"TRT_MAGIC_PARAM": f"{float(trt_magic_param):.6f}f",
|
|
}
|
|
for name, value in defs.items():
|
|
lines = compiler.modify_macro(lines, name, value)
|
|
compiler.write_lines(compiler.kernel_path("macros.h"), lines)
|
|
|
|
|
|
def build_flags_2d(nx, ny, cx, cy, radius):
|
|
n = nx * ny
|
|
flag = np.ones(n, dtype=np.uint8) * FLUID_FLAG
|
|
for y in range(ny):
|
|
for x in range(nx):
|
|
k = y * nx + x
|
|
if y == 0 or y == ny - 1 or x == 0 or x == nx - 1:
|
|
flag[k] = SOLID_FLAG
|
|
elif (x - cx) ** 2 + (y - cy) ** 2 < radius ** 2:
|
|
flag[k] = OBSTACLE_FLAG
|
|
return flag
|
|
|
|
|
|
def build_flags_3d(nx, ny, nz, cx, cy, radius):
|
|
n = nx * ny * nz
|
|
flag = np.ones(n, dtype=np.uint8) * FLUID_FLAG
|
|
for z in range(nz):
|
|
for y in range(ny):
|
|
for x in range(nx):
|
|
k = z * ny * nx + y * nx + x
|
|
if y == 0 or y == ny - 1 or x == 0 or x == nx - 1:
|
|
flag[k] = SOLID_FLAG
|
|
elif (x - cx) ** 2 + (y - cy) ** 2 < radius ** 2:
|
|
flag[k] = OBSTACLE_FLAG
|
|
return flag
|
|
|
|
|
|
def run_case(device_id, cfg):
|
|
nx, ny, nz = cfg["nx"], cfg["ny"], cfg["nz"]
|
|
dim, nq = cfg["dim"], cfg["nq"]
|
|
n = nx * ny * nz
|
|
|
|
set_macros(
|
|
nx=nx,
|
|
ny=ny,
|
|
nz=nz,
|
|
dim=dim,
|
|
nq=nq,
|
|
vis=cfg["vis"],
|
|
u0=cfg["u0"],
|
|
collision_model=cfg["collision_model"],
|
|
use_les=cfg["use_les"],
|
|
les_cs=cfg["les_cs"],
|
|
outlet_mode=cfg["outlet_mode"],
|
|
outlet_backflow_clamp=cfg["outlet_backflow_clamp"],
|
|
outlet_blend_alpha=cfg["outlet_blend_alpha"],
|
|
omega_collision_max=cfg["omega_collision_max"],
|
|
inlet_profile=cfg["inlet_profile"],
|
|
trt_magic_param=cfg["trt_magic_param"],
|
|
)
|
|
compiler.compile_kernel_v2()
|
|
|
|
cuda.init()
|
|
dev = cuda.Device(device_id)
|
|
ctx = dev.make_context()
|
|
try:
|
|
mod = cuda.module_from_file(compiler.kernel_path("kernel_v2.ptx"))
|
|
init_fn = mod.get_function("InitTubeFlow_v2")
|
|
step_fn = mod.get_function("OneStep")
|
|
|
|
params_ptr, params_size = mod.get_global("d_params")
|
|
params_data = struct.pack(
|
|
"IIIQfffffffI",
|
|
nx,
|
|
ny,
|
|
nz,
|
|
n,
|
|
cfg["omega"],
|
|
1.1,
|
|
0.0,
|
|
0.0,
|
|
0.0,
|
|
1.0,
|
|
cfg["u0"],
|
|
0,
|
|
)
|
|
if len(params_data) < params_size:
|
|
params_data += b"\x00" * (params_size - len(params_data))
|
|
cuda.memcpy_htod(params_ptr, params_data)
|
|
|
|
fsize = n * nq * 4
|
|
d_fi = cuda.mem_alloc(fsize)
|
|
d_fi2 = cuda.mem_alloc(fsize)
|
|
d_flag = cuda.mem_alloc(n)
|
|
d_indx = cuda.mem_alloc(n * 4)
|
|
d_delta = cuda.mem_alloc(4)
|
|
d_action = cuda.mem_alloc(4)
|
|
d_obs = cuda.mem_alloc(4)
|
|
|
|
cuda.memset_d32(d_indx, 0, n)
|
|
cuda.memset_d32(d_delta, 0, 1)
|
|
cuda.memset_d32(d_action, 0, 1)
|
|
cuda.memset_d32(d_obs, 0, 1)
|
|
|
|
block = (128, 1, 1)
|
|
grid = ((nx + 127) // 128, ny, nz)
|
|
|
|
init_fn(d_flag, d_fi, block=block, grid=grid)
|
|
cuda.memcpy_htod(d_flag, cfg["flag"])
|
|
|
|
host0 = np.empty(n * nq, dtype=np.float32)
|
|
cuda.memcpy_dtoh(host0, d_fi)
|
|
host0 = impose_rest_state_on_nonfluid(cfg, host0)
|
|
cuda.memcpy_htod(d_fi, host0)
|
|
cuda.memcpy_htod(d_fi2, host0)
|
|
|
|
diag0 = compute_case_diagnostics(cfg, host0)
|
|
mass0 = diag0["mass"]
|
|
inlet_var0 = diag0["inlet_var"]
|
|
|
|
t0 = time.time()
|
|
for step in range(cfg["steps"]):
|
|
step_fn(d_flag, d_fi, d_fi2, d_indx, d_delta, d_action, d_obs, block=block, grid=grid)
|
|
d_fi, d_fi2 = d_fi2, d_fi
|
|
|
|
if (step + 1) % cfg["report_every"] == 0:
|
|
cuda.Context.synchronize()
|
|
host = np.empty(n * nq, dtype=np.float32)
|
|
cuda.memcpy_dtoh(host, d_fi)
|
|
if nq == 9:
|
|
rho = host.reshape(nq, ny, nx).sum(axis=0)
|
|
c = float(rho[ny // 2, nx // 2])
|
|
else:
|
|
rho = host.reshape(nq, nz, ny, nx).sum(axis=0)
|
|
c = float(rho[nz // 2, ny // 2, nx // 2])
|
|
nan_count = int(np.isnan(rho).sum())
|
|
print(f" step {step+1:7d}: rho_center={c:.6f}, nan={nan_count}")
|
|
if nan_count > 0:
|
|
break
|
|
|
|
cuda.Context.synchronize()
|
|
elapsed = time.time() - t0
|
|
|
|
host = np.empty(n * nq, dtype=np.float32)
|
|
cuda.memcpy_dtoh(host, d_fi)
|
|
if nq == 9:
|
|
rho = host.reshape(nq, ny, nx).sum(axis=0)
|
|
center = float(rho[ny // 2, nx // 2])
|
|
else:
|
|
rho = host.reshape(nq, nz, ny, nx).sum(axis=0)
|
|
center = float(rho[nz // 2, ny // 2, nx // 2])
|
|
|
|
ok, reason = validate_case(rho)
|
|
diag_end = compute_case_diagnostics(cfg, host)
|
|
|
|
if np.isfinite(mass0) and mass0 != 0.0 and np.isfinite(diag_end["mass"]):
|
|
mass_drift = abs(diag_end["mass"] - mass0) / abs(mass0)
|
|
else:
|
|
mass_drift = float("nan")
|
|
|
|
if np.isfinite(inlet_var0) and inlet_var0 > 0.0 and np.isfinite(diag_end["inlet_var"]):
|
|
inlet_var_ratio_to_init = diag_end["inlet_var"] / inlet_var0
|
|
else:
|
|
inlet_var_ratio_to_init = float("nan")
|
|
|
|
plot_path = None
|
|
if cfg.get("save_plot", True):
|
|
plot_path = plot_case(cfg, host, cfg["out_dir"])
|
|
|
|
trt_les_map_path = None
|
|
if cfg.get("save_trt_les_maps", True):
|
|
trt_les_map_path = plot_trt_les_maps(cfg, host, cfg["out_dir"])
|
|
|
|
return {
|
|
"case_tag": make_case_tag(cfg),
|
|
"name": cfg["name"],
|
|
"target_re": cfg["target_re"],
|
|
"steps": cfg["steps"],
|
|
"mlups": float(n * cfg["steps"] / elapsed / 1e6),
|
|
"nan_count": int(np.isnan(rho).sum()),
|
|
"rho_center": center,
|
|
"rho_min": float(np.nanmin(rho)),
|
|
"rho_max": float(np.nanmax(rho)),
|
|
"mass0": float(mass0),
|
|
"mass_end": float(diag_end["mass"]),
|
|
"mass_drift": float(mass_drift),
|
|
"inlet_var0": float(inlet_var0),
|
|
"inlet_var_end": float(diag_end["inlet_var"]),
|
|
"inlet_var_ratio_to_init": float(inlet_var_ratio_to_init),
|
|
"inlet_line_rel_l2": float(diag_end.get("inlet_line_rel_l2", float("nan"))),
|
|
"inlet_line_rel_linf": float(diag_end.get("inlet_line_rel_linf", float("nan"))),
|
|
"inlet_wave_ux_rel": float(diag_end.get("inlet_wave_ux_rel", float("nan"))),
|
|
"inlet_wave_rho_rel": float(diag_end.get("inlet_wave_rho_rel", float("nan"))),
|
|
"wake_checker_rel": float(diag_end.get("wake_checker_rel", float("nan"))),
|
|
"wake_checker_anti_corr_x": float(diag_end.get("wake_checker_anti_corr_x", float("nan"))),
|
|
"omega": cfg["omega"],
|
|
"vis": cfg["vis"],
|
|
"collision_model": cfg["collision_model"],
|
|
"use_les": bool(cfg["use_les"]),
|
|
"les_cs": float(cfg["les_cs"]),
|
|
"outlet_mode": int(cfg["outlet_mode"]),
|
|
"outlet_backflow_clamp": int(cfg["outlet_backflow_clamp"]),
|
|
"outlet_blend_alpha": float(cfg["outlet_blend_alpha"]),
|
|
"inlet_profile": int(cfg["inlet_profile"]),
|
|
"omega_collision_max": float(cfg["omega_collision_max"]),
|
|
"trt_magic_param": float(cfg["trt_magic_param"]),
|
|
"pass": bool(ok),
|
|
"reason": reason,
|
|
"plot_path": plot_path,
|
|
"trt_les_map_path": trt_les_map_path,
|
|
}
|
|
finally:
|
|
ctx.pop()
|
|
|
|
|
|
def build_case_2d(re2d, steps2d, collision_model, use_les, les_cs, out_dir,
|
|
outlet_mode, outlet_backflow_clamp, outlet_blend_alpha,
|
|
omega_collision_max, inlet_profile, trt_magic_param):
|
|
nx, ny, nz = 512, 256, 1
|
|
cx, cy, radius = 128.0, 128.0, 24.0
|
|
u0 = 0.03
|
|
vis, omega = compute_vis_omega(re2d, 2.0 * radius, u0)
|
|
return {
|
|
"name": "2D_D2Q9_highRe",
|
|
"dim": 2,
|
|
"nq": 9,
|
|
"nx": nx,
|
|
"ny": ny,
|
|
"nz": nz,
|
|
"cx": cx,
|
|
"cy": cy,
|
|
"radius": radius,
|
|
"flag": build_flags_2d(nx, ny, cx, cy, radius),
|
|
"u0": u0,
|
|
"vis": vis,
|
|
"omega": omega,
|
|
"steps": steps2d,
|
|
"report_every": max(steps2d // 10, 1),
|
|
"collision_model": collision_model,
|
|
"use_les": use_les,
|
|
"les_cs": les_cs,
|
|
"outlet_mode": int(outlet_mode),
|
|
"outlet_backflow_clamp": int(outlet_backflow_clamp),
|
|
"outlet_blend_alpha": float(outlet_blend_alpha),
|
|
"inlet_profile": int(inlet_profile),
|
|
"omega_collision_max": float(omega_collision_max),
|
|
"trt_magic_param": float(trt_magic_param),
|
|
"target_re": re2d,
|
|
"save_plot": True,
|
|
"save_trt_les_maps": True,
|
|
"out_dir": out_dir,
|
|
}
|
|
|
|
def build_case_3d(re3d, steps3d, collision_model, use_les, les_cs, out_dir,
|
|
outlet_mode, outlet_backflow_clamp, outlet_blend_alpha,
|
|
omega_collision_max, inlet_profile, trt_magic_param):
|
|
nx, ny, nz = 256, 128, 32
|
|
cx, cy, radius = 64.0, 64.0, 12.0
|
|
u0 = 0.04
|
|
vis, omega = compute_vis_omega(re3d, 2.0 * radius, u0)
|
|
return {
|
|
"name": "3D_D3Q19_highRe",
|
|
"dim": 3,
|
|
"nq": 19,
|
|
"nx": nx,
|
|
"ny": ny,
|
|
"nz": nz,
|
|
"cx": cx,
|
|
"cy": cy,
|
|
"radius": radius,
|
|
"flag": build_flags_3d(nx, ny, nz, cx, cy, radius),
|
|
"u0": u0,
|
|
"vis": vis,
|
|
"omega": omega,
|
|
"steps": steps3d,
|
|
"report_every": max(steps3d // 10, 1),
|
|
"collision_model": collision_model,
|
|
"use_les": use_les,
|
|
"les_cs": les_cs,
|
|
"outlet_mode": int(outlet_mode),
|
|
"outlet_backflow_clamp": int(outlet_backflow_clamp),
|
|
"outlet_blend_alpha": float(outlet_blend_alpha),
|
|
"inlet_profile": int(inlet_profile),
|
|
"omega_collision_max": float(omega_collision_max),
|
|
"trt_magic_param": float(trt_magic_param),
|
|
"target_re": re3d,
|
|
"save_plot": True,
|
|
"save_trt_les_maps": True,
|
|
"out_dir": out_dir,
|
|
}
|
|
|
|
|
|
def build_comprehensive_cases(args, out_dir):
|
|
cases = []
|
|
# Coverage matrix at moderate Re to verify all changed pathways.
|
|
for cm in (0, 1, 2):
|
|
for les in (0, 1):
|
|
cases.append(build_case_2d(re2d=200.0, steps2d=args.matrix_steps2d,
|
|
collision_model=cm, use_les=les,
|
|
les_cs=args.les_cs, out_dir=out_dir,
|
|
outlet_mode=args.outlet_mode,
|
|
outlet_backflow_clamp=1,
|
|
outlet_blend_alpha=args.outlet_blend_alpha,
|
|
omega_collision_max=args.omega_collision_max,
|
|
inlet_profile=args.inlet_profile,
|
|
trt_magic_param=args.trt_magic_param))
|
|
cases.append(build_case_3d(re3d=200.0, steps3d=args.matrix_steps3d,
|
|
collision_model=cm, use_les=les,
|
|
les_cs=args.les_cs, out_dir=out_dir,
|
|
outlet_mode=args.outlet_mode,
|
|
outlet_backflow_clamp=1,
|
|
outlet_blend_alpha=args.outlet_blend_alpha,
|
|
omega_collision_max=args.omega_collision_max,
|
|
inlet_profile=args.inlet_profile,
|
|
trt_magic_param=args.trt_magic_param))
|
|
return cases
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="High-Re validation for kernel_v2")
|
|
parser.add_argument("--device", type=int, default=0)
|
|
parser.add_argument("--re2d", type=float, default=5000.0)
|
|
parser.add_argument("--re3d", type=float, default=3000.0)
|
|
parser.add_argument("--steps2d", type=int, default=10000)
|
|
parser.add_argument("--steps3d", type=int, default=20000)
|
|
parser.add_argument("--collision", type=int, default=1, choices=[0, 1, 2],
|
|
help="0=SRT, 1=TRT, 2=MRT")
|
|
parser.add_argument("--use-les", action="store_true", default=True,
|
|
help="Enable Smagorinsky LES")
|
|
parser.add_argument("--no-les", action="store_false", dest="use_les")
|
|
parser.add_argument("--les-cs", type=float, default=0.16)
|
|
parser.add_argument("--outlet-mode", type=int, default=0, choices=[0, 1, 2],
|
|
help="0=non-equilibrium extrapolation, 1=zero-gradient copy, 2=damped blend")
|
|
parser.add_argument("--inlet-profile", type=int, default=1, choices=[0, 1],
|
|
help="0=uniform inlet, 1=parabolic inlet")
|
|
parser.add_argument("--outlet-blend-alpha", type=float, default=0.70,
|
|
help="Blend alpha for outlet-mode 2")
|
|
parser.add_argument("--omega-collision-max", type=float, default=1.999,
|
|
help="Upper clamp for collision omega")
|
|
parser.add_argument("--trt-magic-param", type=float, default=0.002,
|
|
help="TRT magic parameter Lambda used in omega- mapping")
|
|
parser.add_argument("--only", choices=["2d", "3d", "both"], default="both")
|
|
parser.add_argument("--comprehensive", action="store_true",
|
|
help="Run coverage matrix: SRT/TRT/MRT x LES on/off for 2D and 3D")
|
|
parser.add_argument("--matrix-steps2d", type=int, default=1000)
|
|
parser.add_argument("--matrix-steps3d", type=int, default=600)
|
|
args = parser.parse_args()
|
|
|
|
macro_path = compiler.kernel_path("macros.h")
|
|
macro_backup = compiler.read_lines(macro_path)
|
|
|
|
out_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "output")
|
|
os.makedirs(out_dir, exist_ok=True)
|
|
out_json = os.path.join(out_dir, "high_re_validation_summary.json")
|
|
|
|
try:
|
|
results = []
|
|
|
|
if args.only in ("2d", "both"):
|
|
c2 = build_case_2d(args.re2d, args.steps2d, args.collision, args.use_les,
|
|
args.les_cs, out_dir, args.outlet_mode, 1,
|
|
args.outlet_blend_alpha, args.omega_collision_max,
|
|
args.inlet_profile, args.trt_magic_param)
|
|
print("\n=== Running 2D high-Re case ===")
|
|
print(f" target Re={args.re2d:.1f}, vis={c2['vis']:.6e}, omega={c2['omega']:.6f}")
|
|
results.append(run_case(args.device, c2))
|
|
|
|
if args.only in ("3d", "both"):
|
|
c3 = build_case_3d(args.re3d, args.steps3d, args.collision, args.use_les,
|
|
args.les_cs, out_dir, args.outlet_mode, 1,
|
|
args.outlet_blend_alpha, args.omega_collision_max,
|
|
args.inlet_profile, args.trt_magic_param)
|
|
print("\n=== Running 3D high-Re case ===")
|
|
print(f" target Re={args.re3d:.1f}, vis={c3['vis']:.6e}, omega={c3['omega']:.6f}")
|
|
results.append(run_case(args.device, c3))
|
|
|
|
if args.comprehensive:
|
|
print("\n=== Running comprehensive coverage matrix ===")
|
|
for cfg in build_comprehensive_cases(args, out_dir):
|
|
print(f" {cfg['name']} Re={cfg['target_re']:.1f} "
|
|
f"{collision_name(cfg['collision_model'])} LES={int(cfg['use_les'])}")
|
|
results.append(run_case(args.device, cfg))
|
|
|
|
with open(out_json, "w", encoding="utf-8") as f:
|
|
json.dump(results, f, indent=2)
|
|
|
|
print("\n=== Summary ===")
|
|
n_pass = 0
|
|
for r in results:
|
|
if r["pass"]:
|
|
n_pass += 1
|
|
print(f"{r['name']}: nan={r['nan_count']}, rho_center={r['rho_center']:.6f}, "
|
|
f"rho[min,max]=[{r['rho_min']:.6f}, {r['rho_max']:.6f}], "
|
|
f"mass_drift={r['mass_drift']:.3e}, inlet_var_end={r['inlet_var_end']:.3e}, "
|
|
f"inlet_relL2={r['inlet_line_rel_l2']:.3e}, inlet_relLinf={r['inlet_line_rel_linf']:.3e}, "
|
|
f"waveUx={r['inlet_wave_ux_rel']:.3e}, waveRho={r['inlet_wave_rho_rel']:.3e}, "
|
|
f"chkRel={r['wake_checker_rel']:.3e}, chkAntiX={r['wake_checker_anti_corr_x']:.3e}, "
|
|
f"MLUPS={r['mlups']:.1f}, pass={r['pass']} ({r['reason']})")
|
|
if r.get("plot_path"):
|
|
print(f" plot: {r['plot_path']}")
|
|
if r.get("trt_les_map_path"):
|
|
print(f" trt_les_map: {r['trt_les_map_path']}")
|
|
print(f"Pass rate: {n_pass}/{len(results)}")
|
|
print(f"Saved: {out_json}")
|
|
finally:
|
|
compiler.write_lines(macro_path, macro_backup)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|