Files
NotBigGhostandClaude Opus 5 11ff7b79b4 Начальный коммит: Vulkan-редактор SimVulcan + исследование KBC-LBM
Состояние на момент заведения репозитория.

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>
2026-08-14 16:37:38 +03:00

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)")