Начальный коммит: 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>
This commit is contained in:
2026-08-14 16:37:38 +03:00
co-authored by Claude Opus 5
commit 11ff7b79b4
139 changed files with 88747 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
# Общая инфраструктура GPU-демо (ноутбук kbc_lbm.ipynb код не исполняет — только встраивает
# артефакты, которые генерят эти скрипты на GPU-сервере).
#
# Физика НЕ дублируется: решётка/равновесие/столкновение/перенос импортируются из
# ../solver_2x_sdf (те же модули, что и в финальной модели) — демо тем самым доказывают,
# что КОМПОНЕНТЫ финальной модели воспроизводят классические бенчмарки.
# Здесь только то, чего в модели нет намеренно: полиномиальное равновесие и BGK
# (для сравнений «BGK vs KBC»), H-функция, γ-поле для визуализации, гифки.
import os
import sys
import io as _io
HERE = os.path.dirname(os.path.abspath(__file__))
THEORY = os.path.dirname(HERE)
sys.path.insert(0, os.path.join(THEORY, "solver_2x_sdf"))
import backend as B # GPU-only: жёсткая ошибка без CuPy (CPU-фолбэка нет намеренно)
import lattice as L
import equilibrium as E
import collision as Coll
import streaming as Strm
import visualization as Viz # make_progress (ASCII-бар)
cp = B.cp; xp = B.xp; DTYPE = B.DTYPE; to_cpu = B.to_cpu
Q = L.Q; CS2 = L.CS2; OPP = L.OPP; Cx = L.Cx; Cy = L.Cy
import numpy as np
# пути артефактов: фигуры/гифки — общие каталоги theory, числа — demos_gpu/out
FIGDIR = os.path.join(THEORY, "figures"); os.makedirs(FIGDIR, exist_ok=True)
ANIMDIR = os.path.join(THEORY, "anim"); os.makedirs(ANIMDIR, exist_ok=True)
OUTDIR = os.path.join(HERE, "out"); os.makedirs(OUTDIR, exist_ok=True)
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
try:
sys.stdout.reconfigure(encoding="utf-8") # кириллица/греческие в print при cp1251-консоли
except Exception:
pass
plt.rcParams.update({
"figure.dpi": 110, "savefig.dpi": 120, "font.size": 11,
"axes.titlesize": 13, "axes.titleweight": "bold",
"axes.grid": True, "grid.alpha": 0.25, "axes.axisbelow": True,
"figure.facecolor": "white", "axes.facecolor": "#fbfbfd",
})
# пороги самопроверок под точность бэкенда (демо обычно гоняются в float32)
FP64 = (DTYPE == cp.float64)
TOL_EXACT = 1e-12 if FP64 else 2e-5 # «точные» тождества (моменты равновесия и т.п.)
def finish(fig, name):
"""Сохранить фигуру в figures/ под тем же именем, что использовал ноутбук."""
fig.savefig(os.path.join(FIGDIR, name), bbox_inches="tight")
plt.close(fig)
print("PNG:", name)
def write_out(name, lines):
"""Дописать результаты в demos_gpu/out/<name> (ноутбук цитирует эти числа в markdown)."""
with open(os.path.join(OUTDIR, name), "w", encoding="utf-8") as fh:
fh.write("\n".join(lines) + "\n")
print("OUT:", name)
# ----- дополнения к модели (нужны только демо) ------------------------------------------------
def feq_poly(rho, u):
"""Полиномиальное равновесие O(u²) — стандарт BGK (в модели НЕ используется; здесь —
для демонстрации «BGK+poly разрушается там, где KBC+entropic держит»)."""
CXa = L.CXa[:, None, None]; CYa = L.CYa[:, None, None]; Wa = L.Wa[:, None, None]
cu = CXa*u[0] + CYa*u[1]
usq = u[0]**2 + u[1]**2
return Wa * rho * (1.0 + cu/CS2 + cu**2/(2.0*CS2*CS2) - usq/(2.0*CS2))
def bgk_collide(f, fe, beta):
"""Классический BGK: f − ω(f−feq), ω=2β. Эквивалент KBC при γ=2."""
return f - 2.0*beta*(f - fe)
def kbc_gamma(f, fe, beta):
"""Поле энтропийного стабилизатора γ (для визуализации; сам оператор — collision.kbc_collide,
формула та же: γ = 1/β − (2−1/β)·⟨Δs|Δh⟩/⟨Δh|Δh⟩)."""
dfn = f - fe
ds = Coll._project_shift(dfn)
dh = dfn - ds; inv = 1.0/fe
num = (ds*dh*inv).sum(0); den = (dh*dh*inv).sum(0)
nrm = (dfn*dfn*inv).sum(0) # тот же относительный порог, что в collision.py
ok = den > B.GREL*nrm
return xp.where(ok, 1.0/beta - (2.0 - 1.0/beta)*num/xp.where(ok, den, B.ONE), B.TWO)
def nu_to_beta(nu):
return 1.0 / (2.0 * (nu / CS2 + 0.5))
def H_function(f):
"""Дискретная H-функция H = Σ f_i ln(f_i/w_i) (поле)."""
fp = xp.maximum(f, 1e-30)
return (fp * xp.log(fp / L.Wa[:, None, None])).sum(0)
def smoothstep(x):
x = min(max(x, 0.0), 1.0)
return x*x*(3.0 - 2.0*x)
# ----- host-постобработка (numpy: кадры уже перенесены на хост) --------------------------------
def vorticity(u):
"""ω = ∂x u_y − ∂y u_x центральными разностями (host, по кадру)."""
duy_dx = (np.roll(u[1], -1, 1) - np.roll(u[1], 1, 1)) * 0.5
dux_dy = (np.roll(u[0], -1, 0) - np.roll(u[0], 1, 0)) * 0.5
return duy_dx - dux_dy
def render_gif(n_frames, draw_fn, name, figsize, fps=18, dpi=80):
"""Отрисовать n_frames кадров (draw_fn(fig, i)) и сохранить зацикленный GIF в anim/."""
from PIL import Image
path = os.path.join(ANIMDIR, name)
fig = plt.figure(figsize=figsize, dpi=dpi)
pil = []
for i in range(n_frames):
fig.clf()
draw_fn(fig, i)
buf = _io.BytesIO()
fig.savefig(buf, format="png")
buf.seek(0)
pil.append(Image.open(buf).convert("RGB").convert("P", palette=Image.ADAPTIVE))
plt.close(fig)
pil[0].save(path, save_all=True, append_images=pil[1:],
duration=int(1000 / fps), loop=0, optimize=True)
print("GIF:", name, f"({n_frames} кадров)")
return path