Сплошная проверка документов против дерева. Код не менялся. docs/rust_vs_cpp.md — числа, снятые с чужого каталога сборки: - «775 МБ исходников зависимостей» и «10 пакетов в графе C++» получены на build/, сконфигурированном из другого дерева (его _deps содержит 262 МБ nlohmann_json, который проект не объявляет). На деле девять объявленных репозиториев дают ~400 МБ исходников; - пакетов в графе Rust 82, а не 76 (cargo tree по текущему Cargo.lock); - итог по строкам 2976/494, а не 2962/492: строка «меш» устарела на 17 строк игнорируемого диагностического теста, тесты в модулях — 176; - «перевес почти весь в Vulkan-слое» — на деле 346 из 542 (64 %), из них 305 на Context и Swapchain; остальное приходится на меш, редактор и приложение; - «ни один замер не даёт разницы в порядок величины» неверно: инкрементальная release — 52.3 против 4.4 с, это 11.9x; - граней с пятью и более вершинами в plane.obj 119, а не 121; - из Catch2 перенесены все три случая, и добавлено ещё два, а не «три перенесены дословно»; - деструктор Renderer::Impl — 22 строки, а не тридцать; - путевых зависимостей у порта две: assets/meshes с откатом на текущий каталог и pipeline_cache.bin рядом с бинарём; - граф целей CMake ацикличен, цикл существует на уровне исходников — именно поэтому CMake и молчит. rust/README.md: - объявленный rust-version = "1.82" недостижим: залоченные egui, egui-winit и epaint 0.36.1 требуют 1.95; - перечни зависимостей крейтов были неполны, приведены целиком; - build.rs читает ../../../shaders, а не ../../shaders; - тесты не «чистая математика»: тесты загрузчика читают файлы из assets/meshes и требуют клон репозитория; - assets/ порту никто не копирует, он находит корневую копию сам; - те же поправки про цикл в CMake и про момент ожидания простоя. README.md: - панель называется Mesh, а не «Mesh load»; - пресета clang-cl не существует, компилятор пресеты не фиксируют; - версии Vulkan SDK и компиляторов сборкой не проверяются; - Buffer и Image перечислены среди рабочих модулей vk/, хотя ими не пользуется никто. docs/theory/solver_2x_sdf/README.md: - предлагался несуществующий переключатель cfg.collision="bgk" и реестр операторов get; тот же файл двумя разделами ниже говорит, что оператор зафиксирован. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
163 lines
17 KiB
Markdown
163 lines
17 KiB
Markdown
# 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` | гифка |ω|, png-панель рядов (`series_2x_sdf.png`), прогресс-бар |
|
||
| `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)` —
|
||
проекция на сдвиг-моменты, `γ` — энтропийный лимитер. Оператор жёстко зафиксирован:
|
||
модуль объявляет `collide = kbc_collide`, поля `cfg.collision` и реестра операторов
|
||
не существует (см. раздел «Статус» ниже). Чтобы поэкспериментировать с BGK или TRT,
|
||
придётся вводить и то и другое — это осознанно не сделано.
|
||
- **Перенос** — `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 с печатью трейсбэка.
|