Состояние на момент заведения репозитория. 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>
17 KiB
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=... (подмножество факторов).
Что привело сюда (хронология фиксов):
- Зонд скорости → L1; оценка St с параболической интерполяцией пика.
- Сила → галилей-инвариантный
force_gmem(Wen 2014), готов к подвижным телам. - Стартовое возмущение (поперечный sin-импульс входа) — запуск дорожки за единицы циклов.
- Диагностика по окнам (
convergence_report, трекинг ⟨ρ⟩) — вскрыла дрейф массы. - Ключевой фикс: грубое ГУ выхода (zero-gradient) копило массу → поток глох (Cd/rms→0 за 100k).
Заменено на Zou-He скоростной вход + давление-выход (
boundary.channel_bc) — масса заякорена. - Free-slip стенки (
boundary.free_slip_walls, specular) + умеренно расширенный домен (меньше блокировка). Цилиндр остаётся no-slip. - 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 с печатью трейсбэка.