Files
Physics-Ai-Fluent-Analysis/tools/fbed_figures.py
T
admin 5b16b9e790 feat: 水平卧式流化床空床流场 GPU LBM 仿真 (100 格/m)
- 3D D3Q19 LBM (numba.cuda), 240x130x265=8.27M 单元
- 恒功率风机曲线 dp*Q=P 压力进口, 工况点涌现
- 多孔布风板: 2mm 厚, 6.25% 开孔, 5 度倾斜(-x)
- 4 段 DN300 侧进风 / 567->1200 渐扩 / DN800 排风
- 输出 19 张切面/矢量/压差/流量/分区图 (4x 分辨率)
2026-09-19 13:37:51 +08:00

313 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
水平卧式流化床 空床流场 —— 批量出图
====================================
读取 fbed_fields.npz, 生成大量切面 / 分量 / 压力 / 分区图。
python tools/fbed_figures.py
"""
import os
import sys
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import fbed_simulation as fs
OUT = fs.OUT
BG, FG, MUTED, ACCENT = fs.BG, fs.FG, fs.MUTED, fs.ACCENT
CMAP_FLOW = fs.CMAP_FLOW
XC, YC, ZC = fs.XC, fs.YC, fs.ZC
X0, X1, Y0, Y1, Z0, Z1 = fs.X0, fs.X1, fs.Y0, fs.Y1, fs.Z0, fs.Z1
PPM, DX, SCALE_U = fs.PPM, fs.DX, fs.SCALE_U
D = np.load(os.path.join(OUT, "fbed_fields.npz"))
UX, UY, UZ, RHO, SOLID = D["ux"], D["uy"], D["uz"], D["rho"], D["solid"]
SPEED = np.sqrt(UX ** 2 + UY ** 2 + UZ ** 2) * SCALE_U
P = (RHO - 1.0) / 3.0 * (1.2 * SCALE_U ** 2)
P[SOLID] = np.nan
CMAP_P = plt.get_cmap("RdBu_r")
def xi(v):
return fs._xi(v)
def yi(v):
return fs._yi(v)
def zi(v):
return fs._zi(v)
def get(which, idx):
if which == "xz":
return (SPEED[:, idx, :], UX[:, idx, :], UZ[:, idx, :], SOLID[:, idx, :],
[X0, X1, Z0, Z1], "x (m)", "z (m)")
if which == "yz":
return (SPEED[:, :, idx], UY[:, :, idx], UZ[:, :, idx], SOLID[:, :, idx],
[Y0, Y1, Z0, Z1], "y (m)", "z (m)")
return (SPEED[idx, :, :], UX[idx, :, :], UY[idx, :, :], SOLID[idx, :, :],
[X0, X1, Y0, Y1], "x (m)", "y (m)")
def get_scalar(field, which, idx):
if which == "xz":
return field[:, idx, :], SOLID[:, idx, :], [X0, X1, Z0, Z1], "x (m)", "z (m)"
if which == "yz":
return field[:, :, idx], SOLID[:, :, idx], [Y0, Y1, Z0, Z1], "y (m)", "z (m)"
return field[idx, :, :], SOLID[idx, :, :], [X0, X1, Y0, Y1], "x (m)", "y (m)"
def panel(ax, which, idx, vmax, stream=True, density=1.1):
s2, u2, v2, sol2, ext, xl, yl = get(which, idx)
ax.imshow(np.where(sol2, np.nan, s2), origin="lower", extent=ext, cmap=CMAP_FLOW,
vmin=0, vmax=vmax, interpolation="bilinear", aspect="equal")
if stream:
ny, nx = sol2.shape
xs = np.linspace(ext[0], ext[1], nx)
ys = np.linspace(ext[2], ext[3], ny)
U = np.where(sol2, np.nan, u2)
V = np.where(sol2, np.nan, v2)
with np.errstate(invalid="ignore"):
ax.streamplot(xs, ys, U, V, density=density,
color=(0.85, 0.88, 0.92, 0.38), linewidth=0.45,
arrowsize=0.5, zorder=5)
ax.contour(np.linspace(ext[0], ext[1], sol2.shape[1]),
np.linspace(ext[2], ext[3], sol2.shape[0]),
sol2.astype(float), levels=[0.5], colors="#adbac7", linewidths=0.7, zorder=6)
ax.set_xlabel(xl, fontsize=8)
ax.set_ylabel(yl, fontsize=8)
ax.set_xlim(ext[0], ext[1])
ax.set_ylim(ext[2], ext[3])
ax.tick_params(labelsize=7)
def panel_scalar(ax, field, which, idx, cmap, vmin=None, vmax=None, center=False, stream=True):
f2, sol2, ext, xl, yl = get_scalar(field, which, idx)
f2 = np.where(sol2, np.nan, f2)
if center:
lim = np.nanpercentile(np.abs(f2), 99) * 1.05
im = ax.imshow(f2, origin="lower", extent=ext, cmap=cmap, vmin=-lim, vmax=lim,
interpolation="bilinear", aspect="equal")
else:
im = ax.imshow(f2, origin="lower", extent=ext, cmap=cmap, vmin=vmin, vmax=vmax,
interpolation="bilinear", aspect="equal")
if stream:
s2, u2, v2, _, _, _, _ = get(which, idx)
ny, nx = sol2.shape
xs = np.linspace(ext[0], ext[1], nx)
ys = np.linspace(ext[2], ext[3], ny)
with np.errstate(invalid="ignore"):
ax.streamplot(xs, ys, np.where(sol2, np.nan, u2), np.where(sol2, np.nan, v2),
density=1.0, color=(0.85, 0.88, 0.92, 0.3), linewidth=0.4,
arrowsize=0.5, zorder=5)
ax.contour(np.linspace(ext[0], ext[1], sol2.shape[1]),
np.linspace(ext[2], ext[3], sol2.shape[0]),
sol2.astype(float), levels=[0.5], colors="#adbac7", linewidths=0.7, zorder=6)
ax.set_xlabel(xl, fontsize=8)
ax.set_ylabel(yl, fontsize=8)
ax.set_xlim(ext[0], ext[1])
ax.set_ylim(ext[2], ext[3])
ax.tick_params(labelsize=7)
return im
def montage(specs, ncols, suptitle, name, vmax, cbar_label="|U| (m/s)",
figw=17.0, panel_h=3.0, stream=True, density=1.1):
nrows = (len(specs) + ncols - 1) // ncols
fig, axes = plt.subplots(nrows, ncols, figsize=(figw, panel_h * nrows + 1.0),
squeeze=False)
for ax in axes.ravel()[len(specs):]:
ax.axis("off")
for ax, (spec, title) in zip(axes.ravel(), specs):
which, idx = spec
panel(ax, which, idx, vmax, stream=stream, density=density)
ax.set_title(title, fontsize=9.5, pad=4)
sm = plt.cm.ScalarMappable(cmap=CMAP_FLOW, norm=plt.Normalize(0, vmax))
cb = fig.colorbar(sm, ax=axes.ravel().tolist(), fraction=0.012, pad=0.01)
cb.set_label(cbar_label, fontsize=10)
cb.outline.set_edgecolor("#30363d")
fig.suptitle(suptitle, fontsize=15, y=0.995)
fig.savefig(os.path.join(OUT, name), dpi=500, bbox_inches="tight")
plt.close(fig)
print(" ->", name)
def fig_xy_plenum():
specs = [(("xy", zi(z)), f"z = {z:+.2f} m") for z in (-0.42, -0.32, -0.22, -0.12, -0.02)]
montage(specs, 3, "风箱层水平切面 |U|(4 段侧进风分布)", "fbed-montage-plenum.png", 6.0)
def fig_xy_plate():
specs = [(("xy", zi(z)), f"z = {z:.2f} m") for z in (0.04, 0.08, 0.13, 0.20, 0.30, 0.45)]
montage(specs, 3, "布风板近区水平切面 |U|(射流合并过程)", "fbed-montage-nearplate.png", 3.0)
def fig_xy_free():
specs = [(("xy", zi(z)), f"z = {z:.2f} m") for z in (0.60, 0.80, 1.00, 1.20, 1.45, 1.75)]
montage(specs, 3, "沸腾段 / 渐扩 / 排风 水平切面 |U|", "fbed-montage-freeboard.png", 4.0)
def fig_xz():
specs = [(("xz", yi(y)), f"y = {y:+.2f} m") for y in (-0.26, -0.13, 0.0, 0.13, 0.26)]
montage(specs, 3, "垂直切面 x-z(不同宽度位置)|U|", "fbed-montage-xz.png", 5.0, figw=16.0)
def fig_yz():
specs = [(("yz", xi(x)), f"x = {x:.2f} m") for x in (0.15, 0.45, 0.75, 1.05, 1.35, 1.65, 1.95, 2.25)]
montage(specs, 4, "垂直切面 y-z(沿长度)|U|", "fbed-montage-yz.png", 5.0, figw=18.0, panel_h=3.4)
def fig_components():
fig, axes = plt.subplots(2, 2, figsize=(15.0, 12.0))
panel(axes[0, 0], "xz", yi(0.0), 4.0)
axes[0, 0].set_title("(a) 速度大小 |U|", fontsize=12, color=ACCENT)
for ax, comp, title, cmap in [
(axes[0, 1], UX, "(b) 水平分量 ux(孔板 5° 倾斜 + 向排风汇流)", "coolwarm"),
(axes[1, 0], UY, "(c) 横向分量 uy(侧进风 + 渐扩)", "coolwarm"),
(axes[1, 1], UZ, "(d) 垂直分量 uz(上升主流)", "coolwarm")]:
f2, sol2, ext, xl, yl = get_scalar(comp * SCALE_U, "xz", yi(0.0))
lim = np.nanpercentile(np.abs(np.where(sol2, np.nan, f2)), 99) * 1.05
im = ax.imshow(np.where(sol2, np.nan, f2), origin="lower", extent=ext, cmap=cmap,
vmin=-lim, vmax=lim, interpolation="bilinear", aspect="equal")
ax.contour(np.linspace(ext[0], ext[1], sol2.shape[1]),
np.linspace(ext[2], ext[3], sol2.shape[0]),
sol2.astype(float), levels=[0.5], colors="#adbac7", linewidths=0.7)
ax.set_title(title, fontsize=12)
ax.set_xlabel("x (m)", fontsize=9)
ax.set_ylabel("z (m)", fontsize=9)
cb = fig.colorbar(im, ax=ax, fraction=0.036, pad=0.02)
cb.set_label("m/s", fontsize=9)
cb.outline.set_edgecolor("#30363d")
fig.suptitle("速度分量分解(垂直切面 y=0)", fontsize=15, y=0.99)
fig.tight_layout(rect=[0, 0, 1, 0.97])
fig.savefig(os.path.join(OUT, "fbed-components.png"), dpi=520)
plt.close(fig)
print(" -> fbed-components.png")
def fig_pressure():
fig, axes = plt.subplots(2, 2, figsize=(15.0, 12.0))
for ax, (which, idx, title) in zip(axes.ravel(), [
("xz", yi(0.0), "(a) 垂直切面 y=0 —— 全系统压差"),
("yz", xi(1.2), "(b) 垂直切面 x=1.2"),
("xy", zi(0.15), "(c) 布风板近区 z=0.15"),
("xy", zi(0.90), "(d) 沸腾段 z=0.90")]):
im = panel_scalar(ax, P, which, idx, CMAP_P, center=True, stream=False)
ax.set_title(title, fontsize=12)
cb = fig.colorbar(im, ax=ax, fraction=0.036, pad=0.02)
cb.set_label("静压 (Pa)", fontsize=9)
cb.outline.set_edgecolor("#30363d")
fig.suptitle("静压场(相对出口, 单位 Pa)", fontsize=15, y=0.99)
fig.tight_layout(rect=[0, 0, 1, 0.97])
fig.savefig(os.path.join(OUT, "fbed-pressure.png"), dpi=520)
plt.close(fig)
print(" -> fbed-pressure.png")
def fig_sections():
fig, axes = plt.subplots(1, 4, figsize=(17.0, 6.0))
for k, ax in enumerate(axes):
xc = 0.6 * (k + 0.5)
panel(ax, "yz", xi(xc), 5.0)
ax.set_title(f"第 {k+1} 段 x={xc:.1f} m", fontsize=11)
fig.suptitle("4 个进风区段中心垂直切面 y-z(单段风量 2500 m³/h)", fontsize=14, y=1.0)
fig.tight_layout(rect=[0, 0, 1, 0.95])
fig.savefig(os.path.join(OUT, "fbed-sections.png"), dpi=520)
plt.close(fig)
print(" -> fbed-sections.png")
def fig_hero_stream():
fig, axes = plt.subplots(1, 2, figsize=(18.0, 8.0))
panel(axes[0], "xz", yi(0.0), 4.0, density=1.8)
axes[0].set_title("垂直切面 y=0 —— 风箱/布风板/沸腾段/排风", fontsize=12)
panel(axes[1], "yz", xi(1.2), 4.0, density=1.8)
axes[1].set_title("垂直切面 x=1.2 —— 侧向渐扩", fontsize=12)
fig.suptitle("空床流场流线(GPU D3Q19 LBM)", fontsize=15, y=0.99)
fig.tight_layout(rect=[0, 0, 1, 0.96])
fig.savefig(os.path.join(OUT, "fbed-streamlines.png"), dpi=560)
plt.close(fig)
print(" -> fbed-streamlines.png")
def fig_3d():
from mpl_toolkits.mplot3d import Axes3D # noqa
fig = plt.figure(figsize=(13.0, 10.0))
ax = fig.add_subplot(111, projection="3d")
norm = plt.Normalize(0, 4.0)
def surf_y(iy):
f = SPEED[:, iy, :]
m = SOLID[:, iy, :]
X, Z = np.meshgrid(XC, ZC)
Y = np.full_like(X, YC[iy])
fc = CMAP_FLOW(norm(np.where(m, np.nan, f)))
fc[m, 3] = 0.0
ax.plot_surface(X, Y, Z, facecolors=fc, shade=False, rstride=1, cstride=1,
linewidth=0, antialiased=False)
def surf_x(ix):
f = SPEED[:, :, ix]
m = SOLID[:, :, ix]
Y, Z = np.meshgrid(YC, ZC)
X = np.full_like(Y, XC[ix])
fc = CMAP_FLOW(norm(np.where(m, np.nan, f)))
fc[m, 3] = 0.0
ax.plot_surface(X, Y, Z, facecolors=fc, shade=False, rstride=1, cstride=1,
linewidth=0, antialiased=False)
def surf_z(iz):
f = SPEED[iz, :, :]
m = SOLID[iz, :, :]
X, Y = np.meshgrid(XC, YC)
Z = np.full_like(X, ZC[iz])
fc = CMAP_FLOW(norm(np.where(m, np.nan, f)))
fc[m, 3] = 0.0
ax.plot_surface(X, Y, Z, facecolors=fc, shade=False, rstride=1, cstride=1,
linewidth=0, antialiased=False)
surf_y(yi(0.0))
surf_x(xi(1.2))
surf_z(zi(0.9))
ax.set_xlabel("x (m)")
ax.set_ylabel("y (m)")
ax.set_zlabel("z (m)")
ax.set_title("三正交切面三维剖切(|U|)", fontsize=13)
ax.view_init(elev=22, azim=-58)
sm = plt.cm.ScalarMappable(cmap=CMAP_FLOW, norm=norm)
cb = fig.colorbar(sm, ax=ax, fraction=0.03, pad=0.08, shrink=0.6)
cb.set_label("|U| (m/s)", fontsize=10)
cb.outline.set_edgecolor("#30363d")
fig.savefig(os.path.join(OUT, "fbed-3d-cutaway.png"), dpi=520)
plt.close(fig)
print(" -> fbed-3d-cutaway.png")
def main():
print("[figs] loading", os.path.join(OUT, "fbed_fields.npz"))
fig_xy_plenum()
fig_xy_plate()
fig_xy_free()
fig_xz()
fig_yz()
fig_components()
fig_pressure()
fig_sections()
fig_hero_stream()
fig_3d()
print("[figs] done")
if __name__ == "__main__":
main()