Files
CFDManager/docs/theory/solver_2x_sdf/equilibrium.py
T
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

46 lines
2.4 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.
# Равновесие и макропеременные.
#
# Используется ЭНТРОПИЙНОЕ равновесие в product-form (как в KBC), а не усечённый полином:
# feq_i = w_i · ρ · Π_d (2 − sqrt(1+3 u_d²)) · ((2 u_d + sqrt(1+3 u_d²))/(1 − u_d))^{c_{i,d}}
# Показатели c_{i,d} ∈ {−1,0,+1}, поэтому степень НЕ нужна — берём q или 1/q (быстрее и совместимо
# с CUDA-graph: нет pow и нет host→device для границ clip).
import backend as B
import lattice as L
cp = B.cp; xp = B.xp
CXa = L.CXa; CYa = L.CYa
def _feq9_body(rho, ux, uy):
ux = cp.minimum(cp.maximum(ux, -0.95), 0.95) # clip скоростей литералами (запекается в кернел)
uy = cp.minimum(cp.maximum(uy, -0.95), 0.95)
sx = cp.sqrt(1.0 + 3.0*ux*ux); sy = cp.sqrt(1.0 + 3.0*uy*uy)
base = rho * (2.0 - sx) * (2.0 - sy)
qx = (2.0*ux + sx) / (1.0 - ux); qy = (2.0*uy + sy) / (1.0 - uy)
ix = 1.0/qx; iy = 1.0/qy
return (base*(4.0/9.0), # ( 0, 0)
base*(1.0/9.0)*qx, base*(1.0/9.0)*qy, # (+1,0) (0,+1)
base*(1.0/9.0)*ix, base*(1.0/9.0)*iy, # (-1,0) (0,-1)
base*(1.0/36.0)*qx*qy, base*(1.0/36.0)*ix*qy, # (+1,+1) (-1,+1)
base*(1.0/36.0)*ix*iy, base*(1.0/36.0)*qx*iy) # (-1,-1) (+1,-1)
_feq9 = cp.fuse()(_feq9_body) # одно слитное ядро (если cp.fuse доступен)
_FUSE_OK = [True]
def feq(rho, u):
"""Равновесные популяции (Q,Ny,Nx) по плотности rho и скорости u (индексируется [0],[1]).
Сборка через B.pack (без cupy.stack) — иначе H2D ломает CUDA-graph capture."""
if _FUSE_OK[0]:
try:
return B.pack(_feq9(rho, u[0], u[1]))
except Exception as e: # откат на не-fused (тоже без pow), один раз
print(f"[equilibrium] cp.fuse отключён ({type(e).__name__}: {e}); обычный путь")
_FUSE_OK[0] = False
return B.pack(_feq9_body(rho, u[0], u[1]))
def macros(f):
"""Макропеременные: ρ = Σ_i f_i, u = (Σ_i c_i f_i)/ρ. Возвращает (rho, u=(2,Ny,Nx))."""
r = f.sum(0)
ux = (CXa[:, None, None] * f).sum(0) / r
uy = (CYa[:, None, None] * f).sum(0) / r
return r, B.pack((ux, uy))