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
..

solver_2x_sdf — модель цилиндра 2×+SDF, разложенная по компонентам

Самодостаточный LBM-решатель (D2Q9, KBC) для обтекания цилиндра: грубый уровень L0 плюс один вложенный патч L1 (измельчение ×2). Граница цилиндра — SDF + Bouzidi (no-slip); вход/выход — Zou-He (скорость/давление); верх/низ канала — free-slip (specular). Только GPU/CuPy — CPU-фолбэка нет. Запуск — из этой папки: python run.py (серия по блокировке: python run_blockage.py).

Карта файлов (каждый компонент изолирован и легко меняется)

Файл Ответственность
backend.py жёсткое требование CuPy/GPU, DTYPE, device-скаляры, to_cpu
config.py все параметры модели + производные (τ, β, R, DL, cy/патч из Ny); from_env()
lattice.py D2Q9 (c_i, w_i, OPP) + KBC-проектор Ps на сдвиг-моменты
equilibrium.py feq (product-form, без pow), macros (ρ, u)
collision.py столкновение: kbc_collide (энтропийный KBC-N1) — единственный оператор
streaming.py перенос: stream (пул-схема через roll)
geometry.py маски тела/стенок и SDF для L0 и L1
boundary.py build_bc/apply_bc (SDF+Bouzidi) + channel_bc (Zou-He) + free_slip_walls
forces.py силы: галилей-инвариантный обмен импульсом force_gmem (Fx, Fy, торк Tz) + coefficients (Cd, Cl, Cm)
forces_stress.py кросс-чек силы: ∮σ·n ds по контуру R+δ вокруг тела (независимый эстиматор)
amr.py связка L0↔L1: patch/ghost/fill/restrict
solver.py оркестратор: persistent-буферы, step(), run() (+ CUDA-граф)
diagnostics.py analyze, St (параболическая интерполяция пика), кросс-чек, фиты серии по β
visualization.py гифка
run.py точка входа (один прогон)
run_blockage.py серия по блокировке Ny↑ → β↓, экстраполяция β→0, сводный npz+png
run_factors.py факторное исследование постановки (вход/выход/uy-выход); реестр out/factors.csv

Где какая математика (для ревизии)

  • Столкновение — collision.py. KBC-N1: f ← f − β(2Δs + γΔh), где Δs = Ps·(f−feq) — проекция на сдвиг-моменты, γ — энтропийный лимитер. Чтобы поэкспериментировать: переключить cfg.collision="bgk", либо добавить TRT новой функцией и зарегистрировать в get.
  • Перенос — streaming.py. Чистая пул-схема; на физику влияет только корректность сдвигов.
  • Силы — forces.py. Обмен импульсом по линкам тела: F = Σ_links c_i (f_i^{после столкн.} + f_ī^{после стриминга}). Второй член — из поля ПОСЛЕ стриминга (вернувшаяся Bouzidi-популяция); иначе ведущий симметричный член сокращается и Cd врёт. Сила/момент меряются на каждом подшаге L1 и усредняются (анти-алиасинг; средний Cd не меняется). Торк Tz — вокруг центра тела, точка приложения — пересечение линка со стенкой r_f + q·c_i (Bouzidi-доля q уже лежит в BC); Cm = 2Tz/(U²·DL²), для цилиндра ⟨Cm⟩≈0 — контроль симметрии.
  • Кросс-чек силы — forces_stress.py. Независимый эстиматор — баланс импульса контрольного объёма: F = ∮[σ·n − ρu(u·n)] ds по окружности R+δ (δ=2 тонкие ячейки); σ = −p′·I − (1−1/(2τ₁))·Π^neq_dev (строго девиаторная проекция: след Π^neq в KBC релаксирует с γβ, а не 1/τ). Конвективный член −ρu(u·n) обязателен при δ>0 — без него систематика +4% по ⟨Cd⟩. Считается раз в stress_every шагов вне CUDA-графа; сравнение по средним (⟨Cd⟩, rms Cl) — мгновенные ряды сдвинуты по фазе кольцом R..R+δ. Согласие двух методов = считывание силы корректно.
  • Серия по блокировке — run_blockage.py. D фиксирован, Ny ∈ {90,128,180} → β ∈ {0.178, 0.125, 0.089}; cy и y-границы патча выводятся из Ny автоматически (config.__post_init__). Экстраполяция к β→0 двумя фитами (a+b·β и a+b·β²; спред интерсептов — неопределённость) — прямое сравнение с безграничной литературой без ручной поправки (1−β)².

⚠ Сверка с первоисточниками (2026-08-14): найден дефект, все числа ниже требуют перепрогона

Сверка реализации со статьями в docs/origins (Karlin/Bösch/Chikatamarla, PRE 90, 031302(R) 2014 — принцип; Bösch/Chikatamarla/Karlin, PRE 92, 043309 2015 — алгоритм; arXiv:1507.02509 — 2D-реализация). Ядро KBC-N1 подтверждено точным: проектор Ps совпадает с аналитической s-частью из ур.(10) 2D-статьи (ранг 2, идемпотентен, несёт ровно {N, Π_xy}); равновесие — точный энтропийный максимизатор (сходится с численной максимизацией S при фикс. ρ, ρu); γ по замкнутой формуле ур.(17)/(39) совпадает с корнем точного ур.(15); сдвиг-моменты релаксируют ровно с 2β при любом γ; вязкость по затуханию сдвиговой волны воспроизводится с ошибкой <0.1%.

Дефект — порог GEPS в kbc_collide. Он был АБСОЛЮТНЫМ (1e-6 в float32, режим по умолчанию), тогда как den = ⟨Δh|Δh⟩ квадратична по неравновесию и при разрешённой физике равна ~1e-7…1e-9. Замерено на боевой конфигурации: откат γ←2 срабатывал на 77–99% узлов, то есть решатель считал чистый LBGK, а не KBC. На эталоне 2D-статьи (двойной периодический сдвиговый слой, Re=30000, N=128, разд. VII) это давало +9.3% по энстрофии на t=t_c (0.6599 против эталонных 0.6035); после исправления fp32 даёт 0.6038. Порог сделан относительным (B.GREL, доля от ‖Δ‖²); на тех же полях он теперь не срабатывает нигде (min den/‖Δ‖² ≈ 1.5e-3 против порога 1e-8).

Следствие: все количественные результаты ниже (Cd, rms Cl, St, серия по блокировке, факторное исследование №0/№1) получены на дефектной ветке и подлежат перепрогону. Качественные выводы про ГУ (мода акустического резонатора, роль конвективного члена в кросс-чеке) дефектом не затрагиваются.

Исправлено там же:

  • углы домена: stream() на roll периодичен по обеим осям, а Zou-He стоял на срезе 1:-1 — в 4 угловых узлах вход был напрямую связан с выходом (f[1,0,0] == post[1,0,-1], величина ~0.12). Порядок изменён на free_slip_walls → channel_bc по всему столбцу; заворот закрыт полностью.
  • диагностика ⟨ρ⟩ считалась по всему домену, включая фиктивные узлы внутри цилиндра (смещение −5.4e-4 — ровно тот разряд, в котором ⟨ρ⟩ и цитируется). Теперь только по жидкости.
  • refine был зашит как 2 в tau1, R01 и радиусе тела на L1/контуре σ·n. Обобщено (при r=2 значения бит-в-бит прежние; при r=3 вязкость уровней теперь согласована).

Отдельно — не дефект, а следствие выбора модели. В KBC-N1 след тензора напряжений T отнесён к h-части, поэтому объёмная вязкость плавает: ξ = c_s²(1/(γβ) − ½) (ур.36 статьи 2015, ур.57 2D-статьи). Замер на боевой конфигурации: γ ∈ [−4.1, 4.6], медиана ξ/ν ≈ 17, и на 2.1% узлов ξ < 0 — акустическое АНТИдемпфирование. Это прямо релевантно диагнозу «недодемпфированный акустический резонатор» ниже: KBC-N2 (T в s-части) даёт ξ = ν фиксированно и является каноническим средством именно от этой проблемы. Модель не менялась — решение за автором.

Статус: считывание силы валидировано; идёт факторное исследование постановки

Оператор столкновения зафиксирован — KBC-N1 с энтропийным стабилизатором (канонический Karlin/Bösch/Chikatamarla 2014), без замен (никаких BGK/TRT). Полная документация исследования — в ноутбуке ../kbc_lbm.ipynb (части VI, разделы 22–24).

Считывание силы доказано корректным (прогон 100k, 174×90, 2026-06-09):

  • кросс-чек GMEM vs ∮σ·n: dCd=4.1% (систематика объяснена конвективным членом −ρu(u·n), добавлен в forces_stress.py — ожидаемое расхождение ≲1%);
  • ⟨Cm⟩=−0.00003 (≈0 — симметрия), ⟨ρ⟩=1.001 стабильна, Cd=1.799 воспроизводит прежнюю итерацию бит-в-бит (осреднение по подшагам не сместило среднее).

Эксперимент №0 (серия по блокировке) — гипотеза бокового стеснения ОПРОВЕРГНУТА: Cd(β) немонотонен: 1.799 / 1.771 / 1.870 при Ny=90/128/180; rms Cl на широком канале вырос (0.835), НЧ-модуляция (окна Cd 1.68–1.98). При этом St(β→0)=0.181 ≈ лит. 0.183 — кинематика верна, завышены давленческие амплитуды. Диагноз: продольные границы (вход 2.5D с жёстким профилем; выход 8.3D Zou-He с жёстким uy=0 — отражает вихри).

Эксперимент №1 (прогнан 2026-06-10) — факторное исследование run_factors.py, 6 прогонов при Ny=90 фикс. Итоги (полный разбор — ноутбук, раздел 24; реестр — out/factors.csv):

  • конвективный член подтверждён: кросс-чек B0 4.1%→1.0%, F4 — 0.6%;
  • F4 (мягкий uy-выход) — лучший по всем метрикам: Cd 1.799→1.754, rms Cl 0.705→0.557, St·(1−β)=0.183 (точно лит.), ⟨ρ⟩=1.0000, нулевая НЧ-модуляция;
  • длинные домены без демпфера НЕвалидны (F1/F2/F3 → смещённый режим ⟨ρ⟩≈1.66; F5 → NaN): пара «вход-скорость / выход-давление» — недодемпфированный акустический резонатор (затухание моды ∝ν(π/Nx)²; период осцилляций ⟨ρ⟩ у F5 ≈ 2Nx/c_s — прямая улика).

Эксперимент №2 (в работе) — губка перед выходом: плавный рост ν (×30, smoothstep) в последних sponge_len столбцах L0; β(x) — поле (graph-safe), патч L1 губки не видит. Факторы: F6_sponge (база+uy-extrap+губка 32 — валидация против F4), F7_long_sponge (390×90, вход 6D/выход 18.3D — целевая постановка), F8_long_sp_uy0 (изоляция вклада губки). Критерии: ⟨ρ⟩≈1.000 у всех; F7: Cd≈1.5–1.6, rms Cl<0.45. Затем эксперимент №3 — серия по β на конфигурации F7.

прогон команда артефакты в out/
смоук AMR_STEPS=2000 python run.py graph self-check OK, Cm≈0, кросс-чек ≲1% (с конв. членом)
основной python run.py cyl_2x_sdf.gif, series_2x_sdf.png, probe_2x_sdf.npz
серия по β python run_blockage.py probe_blockage_Ny*.npz, blockage_summary.npz, blockage_extrapolation.png
факторы №2 AMR_FACTORS=F6_sponge,F7_long_sponge,F8_long_sp_uy0 python run_factors.py factor_*.npz, factors.csv (реестр; при смене схемы старый → factors_vN.csv), factors_summary.png

Env-ручки: AMR_NX, AMR_CX (продольная геометрия; x-границы патча L1 выводятся из cx), AMR_OUTLET_UY=zero|extrapolate, AMR_SPONGE_LEN/AMR_SPONGE_NU (губка), AMR_FACTORS=... (подмножество факторов).

Что привело сюда (хронология фиксов):

  1. Зонд скорости → L1; оценка St с параболической интерполяцией пика.
  2. Сила → галилей-инвариантный force_gmem (Wen 2014), готов к подвижным телам.
  3. Стартовое возмущение (поперечный sin-импульс входа) — запуск дорожки за единицы циклов.
  4. Диагностика по окнам (convergence_report, трекинг ⟨ρ⟩) — вскрыла дрейф массы.
  5. Ключевой фикс: грубое ГУ выхода (zero-gradient) копило массу → поток глох (Cd/rms→0 за 100k). Заменено на Zou-He скоростной вход + давление-выход (boundary.channel_bc) — масса заякорена.
  6. Free-slip стенки (boundary.free_slip_walls, specular) + умеренно расширенный домен (меньше блокировка). Цилиндр остаётся no-slip.
  7. Cm + осреднение по подшагам + кросс-чек ∮σ·n ds + серия по блокировке (текущая итерация).

Прямой raw-матч на одной сетке непрактичен (нужен β<0.03 → Ny≈500+); серия из трёх умеренных Ny с экстраполяцией даёт безпоправочное сравнение за ~3 стоимости одного прогона (патч L1 — основная работа — от Ny не зависит).

CUDA Graphs

По умолчанию ВКЛ (cfg.use_cuda_graph, env AMR_CUDAGRAPH=0 — выкл). Захватываются успешно (после замены cupy.stack→backend.pack, отказа от cuBLAS в проекторе KBC и clip-литералов в ядре — всё это убрало host→device при capture). На шаге t=1 — рантайм-самопроверка (граф vs eager); при расхождении/сбое — откат на eager с печатью трейсбэка.