Состояние на момент заведения репозитория. C++ приложение (src/, shaders/, tests/) — минимальный редактор 3D-моделей на Vulkan 1.3: орбитальная камера, три опорные сетки через начало координат, загрузка .obj с режимами отображения. Весь Vulkan изолирован в src/vk/. Исследование (docs/) — оригинальные статьи по KBC (docs/origins) и Python-решатель D2Q9 KBC-N1 с AMR 2x и SDF+Bouzidi (docs/theory). В решателе перед коммитом исправлены дефекты, найденные сверкой с первоисточниками: относительный порог знаменателя энтропийного стабилизатора (абсолютный вырождал KBC в LBGK на 77-99% узлов), заворот вход/выход в углах домена, диагностика средней плотности по фиктивным узлам тела, зашитый refine=2. Подробности — docs/theory/solver_2x_sdf/README.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
123 lines
6.1 KiB
Python
123 lines
6.1 KiB
Python
# Демо-3: каверна с движущейся крышкой (Re=1000) против эталона Ghia, Ghia & Shin (1982).
|
|
# Стенки — bounce-back; крышка — bounce-back с поправкой импульса −2wᵢρ(cᵢ·u_w)/cs².
|
|
# Сетка намеренно грубая, BB первого порядка → совпадение качественное.
|
|
# Артефакты: figures/09_cavity_ghia.png, anim/cavity_transient.gif, out/cavity.txt|npz.
|
|
# Запуск: python cavity_gpu.py
|
|
import numpy as np
|
|
import _common as cm
|
|
|
|
cp = cm.cp; xp = cm.xp; E = cm.E; Coll = cm.Coll; Strm = cm.Strm
|
|
plt = cm.plt; OPP = cm.OPP; Q = cm.Q; CS2 = cm.CS2
|
|
|
|
UP = [2, 5, 6] # c_iy = +1
|
|
DOWN = [4, 7, 8] # c_iy = -1
|
|
RIGHT = [1, 5, 8] # c_ix = +1
|
|
LEFT = [3, 6, 7] # c_ix = -1
|
|
|
|
def run_cavity(N=110, Re=1000.0, U=0.05, steps=14000, tol=1e-7, check=500, frame_every=58):
|
|
nu = U*N/Re
|
|
beta = cm.nu_to_beta(nu)
|
|
rho = cp.ones((N, N), cm.DTYPE)
|
|
u = cp.zeros((2, N, N), cm.DTYPE)
|
|
f = E.feq(rho, u)
|
|
W = cm.to_cpu(cm.L.Wa).astype(float)
|
|
prev = None
|
|
frames = []
|
|
prog = cm.Viz.make_progress("cavity")
|
|
t = 0
|
|
for t in range(steps):
|
|
rho, u = E.macros(f)
|
|
if t % frame_every == 0:
|
|
frames.append((t, cm.to_cpu(u)))
|
|
fcol = Coll.kbc_collide(f, E.feq(rho, u), beta)
|
|
f = Strm.stream(fcol)
|
|
# bounce-back: на каждой стенке достраиваем входящие в область направления
|
|
for i in UP: # низ (y=0)
|
|
f[i, 0, :] = fcol[OPP[i], 0, :]
|
|
for i in RIGHT: # лево (x=0)
|
|
f[i, :, 0] = fcol[OPP[i], :, 0]
|
|
for i in LEFT: # право (x=N-1)
|
|
f[i, :, -1] = fcol[OPP[i], :, -1]
|
|
for i in DOWN: # верх (крышка) + поправка подвижной стенки
|
|
io = OPP[i]
|
|
corr = 2.0*W[io]*(cm.Cx[io]*U)/CS2 # u_w=(U,0): c_y-член равен нулю
|
|
f[i, -1, :] = fcol[io, -1, :] - corr
|
|
if t % check == 0 and t > 0:
|
|
cur = u.copy()
|
|
if prev is not None and float(xp.abs(cur - prev).max()) < tol:
|
|
print(f"\ncavity: сошлось по полю скорости на шаге {t}")
|
|
break
|
|
prev = cur
|
|
prog(t, steps - 1)
|
|
rho, u = E.macros(f)
|
|
return dict(N=N, Re=Re, U=U, u=cm.to_cpu(u), steps=t, frames=frames)
|
|
|
|
cav = run_cavity()
|
|
print(f"Каверна: Re={cav['Re']:.0f}, N={cav['N']}, остановлено на шаге {cav['steps']}")
|
|
|
|
# Эталон Ghia, Ghia & Shin (1982), Re=1000.
|
|
ghia_y = np.array([0.0000, 0.0547, 0.0625, 0.0703, 0.1016, 0.1719, 0.2813, 0.4531,
|
|
0.5000, 0.6172, 0.7344, 0.8516, 0.9531, 0.9609, 0.9688, 0.9766, 1.0000])
|
|
ghia_u = np.array([0.0000, -0.18109, -0.20196, -0.22220, -0.29730, -0.38289, -0.27805,
|
|
-0.10648, -0.06080, 0.05702, 0.18719, 0.33304, 0.46604, 0.51117,
|
|
0.57492, 0.65928, 1.0000])
|
|
ghia_x = np.array([0.0000, 0.0625, 0.0703, 0.0781, 0.0938, 0.1563, 0.2266, 0.2344,
|
|
0.5000, 0.8047, 0.8594, 0.9063, 0.9453, 0.9531, 0.9609, 0.9688, 1.0000])
|
|
ghia_v = np.array([0.0000, 0.27485, 0.29012, 0.30353, 0.32627, 0.37095, 0.33075, 0.32235,
|
|
0.02526, -0.31966, -0.42665, -0.51550, -0.39188, -0.33714, -0.27669,
|
|
-0.21388, 0.0000])
|
|
|
|
Ncav = cav["N"]
|
|
yc = (np.arange(Ncav) + 0.5)/Ncav
|
|
xc = (np.arange(Ncav) + 0.5)/Ncav
|
|
u_centerline = cav["u"][0, :, Ncav//2]/cav["U"]
|
|
v_centerline = cav["u"][1, Ncav//2, :]/cav["U"]
|
|
|
|
fig, axs = plt.subplots(1, 3, figsize=(14, 4.4))
|
|
spd = np.sqrt(cav["u"][0]**2 + cav["u"][1]**2)/cav["U"]
|
|
axs[0].imshow(spd, origin="lower", cmap="viridis", extent=[0, 1, 0, 1])
|
|
sx = np.linspace(0, 1, Ncav)
|
|
axs[0].streamplot(sx, sx, cav["u"][0], cav["u"][1], density=1.1, color="white", linewidth=0.6)
|
|
axs[0].set_title("Линии тока и $|\\mathbf{u}|/U$"); axs[0].set_xlabel("$x/L$"); axs[0].set_ylabel("$y/L$")
|
|
axs[1].plot(u_centerline, yc, "-", color="#1f6feb", label="KBC")
|
|
axs[1].plot(ghia_u, ghia_y, "o", ms=5, mfc="none", color="#d1495b", label="Ghia 1982")
|
|
axs[1].set_title("$u_x/U$ по вертикали $x=0.5$"); axs[1].set_xlabel("$u_x/U$")
|
|
axs[1].set_ylabel("$y/L$"); axs[1].legend()
|
|
axs[2].plot(xc, v_centerline, "-", color="#1f6feb", label="KBC")
|
|
axs[2].plot(ghia_x, ghia_v, "o", ms=5, mfc="none", color="#d1495b", label="Ghia 1982")
|
|
axs[2].set_title("$u_y/U$ по горизонтали $y=0.5$"); axs[2].set_xlabel("$x/L$")
|
|
axs[2].set_ylabel("$u_y/U$"); axs[2].legend()
|
|
fig.suptitle("Демо-3: каверна с движущейся крышкой, Re=1000 (KBC vs Ghia)",
|
|
y=1.04, fontweight="bold")
|
|
cm.finish(fig, "09_cavity_ghia.png")
|
|
|
|
u_interp = np.interp(ghia_y, yc, u_centerline)
|
|
rms = float(np.sqrt(np.mean((u_interp - ghia_u)**2)))
|
|
print(f"Каверна: RMS-отклонение u_x от Ghia ~ {rms:.3f} (грубое BB → допустимо)")
|
|
|
|
# --- гифка становления течения -----------------------------------------------------------------
|
|
_cf = cav["frames"]
|
|
_vmax = max(np.sqrt(u[0]**2 + u[1]**2).max() for _, u in _cf)/cav["U"]
|
|
|
|
def _draw(fig, i):
|
|
t, u = _cf[i]
|
|
ax = fig.add_subplot(111)
|
|
sp = np.sqrt(u[0]**2 + u[1]**2)/cav["U"]
|
|
ax.imshow(sp, origin="lower", cmap="viridis", vmin=0, vmax=_vmax, extent=[0, 1, 0, 1])
|
|
ax.set_title(f"Каверна Re=1000 · t={t} · $|\\mathbf{{u}}|/U$", fontsize=10)
|
|
ax.set_xlabel("$x/L$"); ax.set_ylabel("$y/L$")
|
|
fig.tight_layout()
|
|
|
|
cm.render_gif(len(_cf), _draw, "cavity_transient.gif", (4.9, 4.5), fps=24)
|
|
|
|
np.savez(__import__("os").path.join(cm.OUTDIR, "cavity.npz"),
|
|
yc=yc, xc=xc, u_centerline=u_centerline, v_centerline=v_centerline,
|
|
ghia_y=ghia_y, ghia_u=ghia_u, ghia_x=ghia_x, ghia_v=ghia_v, rms=rms,
|
|
Re=cav["Re"], N=cav["N"], steps=cav["steps"])
|
|
cm.write_out("cavity.txt", [
|
|
f"N={cav['N']} Re={cav['Re']:.0f} U={cav['U']} stopped_at={cav['steps']}",
|
|
f"rms_ux_vs_Ghia={rms:.4f}",
|
|
"PASS каверна: качественное совпадение с Ghia (BB первого порядка)",
|
|
])
|
|
print("DONE (cavity)")
|