Валидационная кампания на 115 прогонов и Docker-образ для сервера
bench/ — список сценариев (порождается gen_scenarios.py, а не правится руками), драйверы под Linux и Windows, предполётная проверка и описание. Девять групп: эталоны первоисточников, цилиндр против литературы, модели стенки и субсеточность, профили крыла, сложная и множественная геометрия, границы домена, старт и время жизни, внутренние инварианты, сверхмелкие сетки до 4096×2048. Стоимость планируется в обновлениях узлов — единственной переносимой между машинами мере; в часы драйвер переводит её по фактическим MLUPS, которые замеряет на месте (--calibrate). Итого 3.76e14 обновлений, ≈90 часов на 4070 Ti. Гифки пишутся на всю длительность прогона в реальном времени, 10 кадр/с. Число кадров этим задано жёстко, поэтому размер регулируется только кадром: замерено 0.103 байта на пиксель после LZW, отсюда бюджет кадры×пиксели с нижней границей по ширине. Итог 4.4 ГБ, самый тяжёлый файл 226 МБ. preflight.py гоняет каждый сценарий на два шага. Окупился сразу: поймал, что вся группа сверхмелких сеток падала на пределе GPU, а девять прогонов группы F передавали --body-x дважды. Оба отказа проявились бы только на сервере, часов через двадцать после старта кампании. Docker: двухстадийная сборка, в образе libvulkan1 и vulkan-tools. NVIDIA Container Toolkit подкладывает Vulkan-ICD только при graphics в NVIDIA_DRIVER_CAPABILITIES — без него wgpu не увидит карту, поэтому capabilities прописаны в образе, а vulkaninfo лежит внутрь для проверки. ENTRYPOINT — драйвер кампании, CMD по умолчанию --dry-run, чтобы случайный docker run не запустил сточасовую задачу. Проверено локально: образ собирается, смоук проходит с монтированием результатов на хост, физика совпадает с хостовой до последней цифры, --backend gpu без карты отказывает явно. GPU-путь в контейнере проверяется только на машине с картой. В README поправлено разделение вкладов в продольную акустику: измерением 2×2 показано, что 74.9% → 1.1% даёт ОДНОРОДНЫЙ СТАРТ, причём при полностью выключенной губке, а губка снимает лишь остаток (1.12% → 0.83% при утроении длины). При старте из покоя губка не помогает совсем — у стоячей четвертьволновой моды там узел давления, где вязкость её не трогает. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+198
-39
@@ -24,6 +24,9 @@ Multi-Relaxation Models for Simulation of Fluid Turbulence* (arXiv:1507.02509,
|
||||
| `src/main.rs` | **запуск и оркестрирование.** Разбор параметров, сборка постановки, цикл по шагам, живой вывод и итоговый отчёт, выгрузка рядов в CSV. Здесь же контракт `Spec` / `StepRec` / `FieldKind`, общий для обоих бэкендов. |
|
||||
| `src/gif.rs` | **создание гифок.** Тайминг относительно физического времени, палитры, нормировка, служебная надпись, кодирование. |
|
||||
|
||||
Рядом, вне этого разделения: `bench/` — валидационная кампания (список сценариев и драйверы под
|
||||
Linux и Windows), `Dockerfile` с `docker-compose.yml` — образ для развёртывания на сервере.
|
||||
|
||||
## Сборка и запуск
|
||||
|
||||
Нужен Rust 1.75+.
|
||||
@@ -31,10 +34,16 @@ Multi-Relaxation Models for Simulation of Fluid Turbulence* (arXiv:1507.02509,
|
||||
```sh
|
||||
cargo build --release # с GPU-бэкендом
|
||||
cargo build --release --no-default-features # только CPU (без wgpu)
|
||||
cargo test --release # 22 быстрых теста
|
||||
cargo test --release -- --include-ignored # плюс эталоны статьи (~18 с)
|
||||
cargo test --release # 29 быстрых тестов
|
||||
cargo test --release -- --include-ignored # плюс 4 эталона статьи (~18 с)
|
||||
```
|
||||
|
||||
Сборка без GPU кладёт бинарь по тому же пути, поэтому собирать её ПОСЛЕДНЕЙ нельзя: `kbc2d`
|
||||
окажется перезаписан вариантом без wgpu, и `--backend gpu` будет отказывать. Порядок —
|
||||
сначала `--no-default-features`, потом обычная.
|
||||
|
||||
На сервер удобнее ставить образом — см. [Развёртывание](#развёртывание-docker).
|
||||
|
||||
Пример: цилиндр Re=150, гифка завихренности в реальном времени.
|
||||
|
||||
```sh
|
||||
@@ -99,19 +108,28 @@ cargo test --release -- --include-ignored # плюс эталоны стат
|
||||
## Что параметризовано
|
||||
|
||||
- **поток**: скорость (м/с), направление, число Рейнольдса, решёточная скорость (число Маха);
|
||||
- **постановка**: `--case channel` — обтекание тела; `taylor-green`, `shear-layer`,
|
||||
`decaying-turbulence` — периодические эталоны из статей, без тела и граничных условий;
|
||||
- **сетка**: размеры домена и размер ячейки в метрах (плотность сетки), коэффициент измельчения
|
||||
вложенного патча и его границы;
|
||||
- **тело**: семь форм на выбор — `cylinder`, `square`, `diamond`, `ellipse`, `naca`, `triangle`,
|
||||
`plate` — плюс характерный размер, относительная толщина, угол атаки и положение;
|
||||
- **тело**: восемь форм на выбор — `cylinder`, `square`, `diamond`, `ellipse`, `naca`,
|
||||
`triangle`, `plate`, `polygon` — плюс характерный размер, относительная толщина, угол атаки и
|
||||
положение. Профиль задаётся четырёхзначным кодом (`--naca 4412`, с кривизной), произвольный
|
||||
контур — списком вершин (`--poly "x,y;x,y;…"`), **несколько тел сразу** — списком
|
||||
`--bodies "cylinder:d=24,x=120,y=120; naca:d=48,x=260,y=120,a=8"` (сила считается по каждому
|
||||
телу отдельно, до четырёх);
|
||||
- **время**: длительность прогона — либо числом шагов (`--steps`), либо прямо в СЕКУНДАХ
|
||||
физического времени (`--time`, число шагов считается как time/δt); начальное поле (однородный
|
||||
поток либо покой с разгоном), длина разгона, амплитуда и длительность стартового возмущения;
|
||||
- **схема**: оператор столкновения (`kbc`/`bgk`), состав сдвиговой части (`n1`/`n2`), модель
|
||||
стенки на теле (`bouzidi`/`grad`/`staircase`), режим выхода, поглощающая губка, бэкенд,
|
||||
число потоков;
|
||||
стенки на теле (`hrr`/`grad`/`bouzidi`/`staircase`), режим выхода, поглощающие губки перед
|
||||
выходом и после входа, бэкенд, число потоков;
|
||||
- **анимация**: файл, поле (`speed`/`vorticity`/`density`/`gamma`), палитра, масштаб, шаг кадра,
|
||||
частота, скорость воспроизведения, диапазон нормировки;
|
||||
- **вывод**: период живых строк, три уровня подробности, число окон в отчёте о сходимости, CSV.
|
||||
частота, скорость воспроизведения, диапазон нормировки, усреднение k×k клеток в пиксель
|
||||
(`--gif-downsample`, без него кадр с сетки 4096×2048 неподъёмен);
|
||||
- **вывод**: период живых строк, три уровня подробности, число окон в отчёте о сходимости, CSV
|
||||
рядов с прореживанием (`--series-every`), x–t диаграмма осевой линии (`--xt`), метрики
|
||||
эталонных течений (`--case-csv`) и машиночитаемая сводка всего прогона (`--summary`).
|
||||
|
||||
## Что печатает отчёт
|
||||
|
||||
@@ -138,7 +156,7 @@ cargo test --release -- --include-ignored # плюс эталоны стат
|
||||
его дословно: ρ, u → f^eq → s и s^eq → Δs = s − s^eq → **Δh = h − h^eq = f − f^eq − Δs** →
|
||||
γ по замкнутой оценке → **f′ = f − β(2Δs + γΔh)**.
|
||||
|
||||
Проверено тестами (`cargo test`, 22 быстрых + 4 длинных):
|
||||
Проверено тестами (`cargo test`, 29 быстрых + 4 длинных):
|
||||
|
||||
- **проектор Δs** совпадает с матричным `M⁻¹·D·M` в базисе натуральных моментов (6)–(7) до 1e-13
|
||||
— для обоих составов сдвиговой части; идемпотентен, не несёт ни массы, ни импульса;
|
||||
@@ -151,7 +169,12 @@ cargo test --release -- --include-ignored # плюс эталоны стат
|
||||
- **объёмная вязкость по ур. (57)**: ξ = ν при следе в сдвиговой части и ξ = c_s²(1/(γβ) − ½)
|
||||
без него;
|
||||
- равновесие в product-form сохраняет ρ и ρu до 1e-13; Zou–He ставит ровно заданные скорость
|
||||
на входе и плотность на выходе; SDF всех семи форм даёт верный знак и |∇φ| = 1 ± 0.05.
|
||||
на входе и плотность на выходе; SDF всех восьми форм даёт верный знак и |∇φ| = 1 ± 0.05
|
||||
(у эллипса для этого пришлось считать ближайшую точку итеративно: дешёвое приближение давало
|
||||
|∇φ| = 0.57 вдали от поверхности);
|
||||
- **HRR-сборка сохраняет моменты**, ради которых затевалась: ρ, ρu и Π восстановленной функции
|
||||
распределения совпадают с целевыми, а при нулевой скорости она совпадает с Градовой (все
|
||||
коэффициенты 3-го порядка рекурсивно обращаются в ноль).
|
||||
|
||||
### Вихрь Тейлора–Грина: второй порядок сходимости (разд. VI)
|
||||
|
||||
@@ -229,31 +252,45 @@ c_s²(1/(γβ) − ½) и, поскольку измеренная ⟨γ⟩ ≈
|
||||
|
||||
Ключ `--wall`:
|
||||
|
||||
- `bouzidi` (умолчание) — интерполированный отскок: доля пересечения q входит в КАЖДУЮ
|
||||
восстанавливаемую популяцию, полинково;
|
||||
- `grad` — условие Града (Dorschner, Bösch, Chikatamarla, Boulouchos, Karlin, JFM 801 (2016),
|
||||
разд. 2.1 и прил. B): задаются не популяции, а целевые моменты — ρ, u и тензор давлений, —
|
||||
после чего недостающие популяции собираются приближением Града (2.13);
|
||||
- `hrr` (умолчание) — восстановление по целевым моментам с **рекурсивной регуляризацией**
|
||||
(Malaspinas 2015; Coreixas и др., PRE 96, 033306): ряд Эрмита продолжен до 3-го порядка, а
|
||||
коэффициенты 3-го порядка не считаются по популяциям, а выражаются через 2-й рекурсивно:
|
||||
`a₃_xxy = 2u_x·a₂_xy + u_y·a₂_xx`, `a₃_xyy = 2u_y·a₂_xy + u_x·a₂_yy`. В D2Q9 `a₃_xxx` и
|
||||
`a₃_yyy` решёткой не поддерживаются и отбрасываются; множитель 1/2c_s⁶ (а не 1/6c_s⁶) учитывает
|
||||
три перестановки индексов;
|
||||
- `grad` — то же самое с обрывом ряда на тензоре давлений: условие Града (Dorschner, Bösch,
|
||||
Chikatamarla, Boulouchos, Karlin, JFM 801 (2016), разд. 2.1 и прил. B). Задаются не популяции,
|
||||
а целевые моменты — ρ, u и Π, — после чего недостающие популяции собираются приближением
|
||||
Града (2.13);
|
||||
- `bouzidi` — интерполированный отскок: доля пересечения q входит в КАЖДУЮ восстанавливаемую
|
||||
популяцию, полинково;
|
||||
- `staircase` — простой отскок, q игнорируется. Не для счёта: это база сравнения, показывающая,
|
||||
сколько именно даёт субсеточность.
|
||||
|
||||
Целевые моменты у `hrr` и `grad` одни и те же — (B 1) и (B 3) прил. B JFM 801; отличается только
|
||||
то, до какого порядка восстанавливается функция распределения по этим моментам.
|
||||
|
||||
**Субсеточность — измеренная.** Прямой тест: сдвигаем тело внутри клетки и смотрим, насколько
|
||||
поедет Cd. У по-настоящему субсеточной границы ответ не должен зависеть от того, где тело стоит
|
||||
относительно узлов (Re = 20, D = 16, стационар, сдвиги 0…½ клетки):
|
||||
относительно узлов (Re = 20, D = 16, стационар, пять положений на полклетки, GPU):
|
||||
|
||||
| модель | разброс Cd | Cd |
|
||||
|---|---|---|
|
||||
| `staircase` | 1.11% | 2.68–2.71 |
|
||||
| `grad` | 0.64% | 2.64–2.66 |
|
||||
| `bouzidi` | **0.19%** | 2.653–2.658 |
|
||||
| `staircase` | 0.98% | 2.482–2.506 |
|
||||
| `grad` | 0.62% | 2.449–2.464 |
|
||||
| `hrr` | 0.61% | 2.450–2.464 |
|
||||
| `bouzidi` | **0.14%** | 2.459–2.463 |
|
||||
|
||||
Град оказывается ровно между ступенькой и Bouzidi, и это следует из его устройства: положение
|
||||
**HRR не улучшает разрешение геометрии и не должен** — 0.61% против 0.62% у Града. Это следует
|
||||
из устройства обеих схем: третий порядок Эрмита уточняет ВОССТАНОВЛЕНИЕ популяций по моментам, а
|
||||
положение стенки входит в моментные схемы совсем другим местом. Обе моментные схемы оказываются
|
||||
ровно между ступенькой и Bouzidi, и это тоже следует из их устройства: положение
|
||||
стенки входит туда ТОЛЬКО через целевую скорость (B 1) — одну усреднённую по узлу величину.
|
||||
Целевая плотность (B 3) — обычная сумма отскочивших и известных популяций, без q вовсе; тензор
|
||||
давлений — конечные разности по решётке, тоже без q. Плюс все недостающие популяции узла
|
||||
собираются из ОДНОГО набора моментов, так что полинковая направленность теряется. Bouzidi же
|
||||
подставляет свою q в каждую популяцию отдельно. Ступенчатой поверхность у Града не становится,
|
||||
но геометрия у него разрешена заметно грубее.
|
||||
подставляет свою q в каждую популяцию отдельно. Ступенчатой поверхность у моментных схем не
|
||||
становится, но геометрия у них разрешена заметно грубее.
|
||||
|
||||
**Сходимость по разрешению тела.** Физическая постановка фиксирована (домен 15D × 10D,
|
||||
блокировка 0.1, Re = 20), меняется только число клеток на диаметр:
|
||||
@@ -269,27 +306,67 @@ c_s²(1/(γβ) − ½) и, поскольку измеренная ⟨γ⟩ ≈
|
||||
Для сравнения, `staircase` при D = 16 даёт 2.69 — то есть +6.7% к пределу, тогда как обе
|
||||
субсеточные модели держатся в пределах +0.4%.
|
||||
|
||||
**Зачем тогда Град.** Его преимущество в статье — не геометрическая точность, а устойчивость
|
||||
на турбулентных режимах (авторы пишут, что интерполяционные схемы «ограничены низкими числами
|
||||
Рейнольдса, поскольку на границе возникают паразитные скачки») и естественная форма для
|
||||
подвижных стенок: скорость стенки входит в целевые значения, а не отдельной поправкой.
|
||||
**Зачем тогда моментные схемы.** Их преимущество в статье — не геометрическая точность, а
|
||||
устойчивость на турбулентных режимах (авторы пишут, что интерполяционные схемы «ограничены
|
||||
низкими числами Рейнольдса, поскольку на границе возникают паразитные скачки») и естественная
|
||||
форма для подвижных стенок: скорость стенки входит в целевые значения, а не отдельной поправкой.
|
||||
В здешней канальной постановке преимущества по устойчивости воспроизвести не удалось: при росте
|
||||
Re обе модели теряют счёт на одном и том же значении (Re ≈ 5·10⁴ при теле в 16 клеток), то есть
|
||||
ограничивает не стенка, а что-то другое — вероятнее всего Zou–He при τ → ½. Поэтому умолчание —
|
||||
`bouzidi`, а `grad` стоит держать в виду для будущих подвижных тел.
|
||||
ограничивает не стенка, а что-то другое — вероятнее всего Zou–He при τ → ½.
|
||||
|
||||
Ограничение реализации: `--wall grad` пока только на процессорном бэкенде; GPU при таком выборе
|
||||
отказывается запускаться явно, а не считает молча по Bouzidi.
|
||||
**Умолчание — `hrr`, и это решение временное.** Оно принято по устройству схемы (третий порядок
|
||||
Эрмита фильтрует высокочастотный мусор у стенки, чего обрыв на Π не делает), а не по здешним
|
||||
измерениям: на стационарном цилиндре при Re = 20 отличить `hrr` от `grad` нельзя вовсе. Вопрос
|
||||
ставит ребром группа C кампании — там обе моментные схемы и Bouzidi гоняются на Re = 20, 150 и
|
||||
2000. Если данные не подтвердят преимущества HRR на турбулентном режиме, умолчанием станет
|
||||
`bouzidi`, у которого измеренное разрешение геометрии вчетверо лучше.
|
||||
|
||||
Все четыре модели работают на обоих бэкендах и совпадают между ними до 0.007% по Cd. Это
|
||||
специально проверяется: раньше GPU при `--wall staircase` молча считал по Bouzidi, и обнаружилось
|
||||
это только потому, что две модели дали побитово одинаковый результат там, где обязаны были
|
||||
разойтись.
|
||||
|
||||
### Паритет бэкендов и согласованность уровней
|
||||
|
||||
CPU (f64) и GPU (f32) на одной постановке совпадают до 4–5 значащих цифр шаг в шаг: ⟨ρ⟩
|
||||
1.04933 против 1.04934, Cd 2.339 против 2.338, ⟨γ⟩ 1.2645 против 1.2646. На Intel Iris Xe GPU
|
||||
даёт ≈105 MLUPS против ≈18 MLUPS у процессора.
|
||||
даёт ≈195 MLUPS против ≈18 MLUPS у процессора.
|
||||
|
||||
Один и тот же случай с патчем ×2 и вовсе без измельчения (`--refine 1`) даёт St 0.1951 против
|
||||
0.1970 и ⟨Cd⟩ 1.956 против 1.943 — связка уровней систематики не вносит.
|
||||
|
||||
**Предел на размер сетки снят.** У GPU есть жёсткий предел `maxComputeWorkgroupsPerDimension`
|
||||
= 65535, а диспетчеризация была одномерной: при 64 узлах на рабочую группу это упирало сетку в
|
||||
4.2 миллиона узлов, то есть примерно 2048×2048. Всё, что крупнее, падало ошибкой валидации —
|
||||
не считало медленно, а не запускалось вовсе. Теперь диспетчеризация двумерная, а линейный
|
||||
индекс собирается в шейдере (`lin()`/`wlin()`); отображение «рабочая группа → узлы» при этом
|
||||
остаётся ровно линейным, поэтому редукции ничего не заметили. Проверено до 4096×4096, паритет
|
||||
с CPU не сдвинулся ни в одной цифре.
|
||||
|
||||
**Где именно кончается f32.** Прямой замер на Тейлоре–Грине, где ошибка известна точно:
|
||||
|
||||
| N | CPU, f64 | GPU, f32 |
|
||||
|---|---|---|
|
||||
| 64 | 9.83e-3 | 9.80e-3 |
|
||||
| 128 | 2.40e-3 | 3.29e-3 |
|
||||
| 256 | 5.97e-4 | 2.37e-2 |
|
||||
|
||||
Пока истинная ошибка выше ~10⁻³, f32 идёт с f64 вровень; ниже — промахивается на порядок и
|
||||
больше. Практический вывод, заложенный в кампанию: исследования сходимости считаются на CPU,
|
||||
всё остальное — на GPU. Двойной точности на GPU здесь быть не может в принципе: **в WGSL типа
|
||||
`f64` не существует**, поэтому wgpu не выразит её ни на каком железе; локальная Iris Xe вдобавок
|
||||
сообщает `shaderFloat64 = false`, а на потребительских NVIDIA f64 идёт в 1/64 от f32 — то есть
|
||||
медленнее, чем CPU.
|
||||
|
||||
Что удалось выжать вместо точности — производительность. Два изменения:
|
||||
|
||||
1. **Батчинг чтения.** Раньше после каждого шага делался `map_async` + `poll(Wait)` ради 48 байт
|
||||
статистики: на 240×120 счёт упирался в 863 шаг/с при том, что сам счёт занимал 0.27 мс из
|
||||
1.16. Теперь итоги копятся в кольце на 128 слотов, синхронизация — раз в батч: **6715 шаг/с**,
|
||||
в 7.8 раза быстрее, при неизменном пошаговом интерфейсе снаружи.
|
||||
2. **Компенсированное суммирование** (Кэхена–Ноймайера) в редукциях и в сумме сил. Наивная сумма
|
||||
по 10⁵–10⁷ узлам съедает ~log₂N бит мантиссы — именно там f32 терял основную точность.
|
||||
|
||||
### Обтекание цилиндра против литературы
|
||||
|
||||
Постановка 480×240, D = 24, Re = 150, блокировка β = D/Ny = 0.1, умолчания решателя:
|
||||
@@ -341,22 +418,101 @@ CPU (f64) и GPU (f32) на одной постановке совпадают
|
||||
Старт из покоя (`--init rest`) разгоняет весь столб жидкости и закачивает моду; при разгоне
|
||||
за 1000 шагов против акустического пробега 831 шаг это для звука удар.
|
||||
- **губка перед выходом включена и подобрана по домену** (`nx/12`, не меньше 16 столбцов,
|
||||
с запасом до патча). Отключается `--sponge-len 0`.
|
||||
с запасом до патча). Отключается `--sponge-len 0`. Вклад у неё скромный — см. таблицу ниже,
|
||||
— но она бесплатна для сил и снимает остаточную пульсацию примерно вдвое при утроении длины.
|
||||
- **`--outlet extrapolate`** — нуль-градиент поперечной скорости; жёсткий ноль отражает вихри
|
||||
дорожки обратно к телу.
|
||||
|
||||
Эффект умолчаний на той же постановке:
|
||||
**Что именно помогает — разделено измерением** (480×240, D=24, Re=150, 30 000 шагов):
|
||||
|
||||
| | размах ⟨ρ⟩ | пульсация u′/U |
|
||||
|---|---|---|
|
||||
| старт из покоя, без губки | 0.1307 | **75%** |
|
||||
| умолчания | 0.0017 | **1.0%** |
|
||||
| старт | губка, столбцов | пульсация u′/U | St | ⟨Cd⟩ | rms Cl |
|
||||
|---|---|---|---|---|---|
|
||||
| uniform | 0 | 1.12% | 0.1839 | 1.483 | 0.404 |
|
||||
| uniform | 40 | 1.03% | 0.1840 | 1.483 | 0.404 |
|
||||
| uniform | 120 | 0.83% | 0.1839 | 1.482 | 0.403 |
|
||||
| rest | 0 | **74.91%** | 0.1534 | 2.009 | 0.807 |
|
||||
| rest | 40 | **74.90%** | 0.1539 | 2.010 | 0.808 |
|
||||
| rest | 120 | **74.99%** | 0.1542 | 2.008 | 0.808 |
|
||||
|
||||
Заодно выправились и сами числа: St 0.183 вместо 0.194, rms Cl 0.38 вместо 0.84, ⟨ρ⟩ 1.0000.
|
||||
Читается однозначно: **весь эффект даёт однородный старт**, 74.9% → 1.12%, причём при
|
||||
полностью выключенной губке. Губка снимает только остаток — 1.12% → 1.03% → 0.83%, — и на
|
||||
силы с частотой схода не влияет вовсе (St 0.1839 во всех трёх строках).
|
||||
|
||||
**При старте из покоя губка не помогает совсем.** Это не осечка реализации, а свойство моды:
|
||||
губка поднимает вязкость на последних столбцах, а там у стоячей четвертьволновой моды **узел
|
||||
давления и пучность скорости** — то самое место, где повышенная вязкость её почти не трогает.
|
||||
Бегущую волну такая губка съедает, стоячую — нет. Убрать моду можно только не возбуждая её.
|
||||
|
||||
Заодно видно, ЧЕМ платит неверный старт: St 0.153 вместо 0.184, ⟨Cd⟩ 2.01 вместо 1.48,
|
||||
rms Cl 0.81 вместо 0.40 — то есть 75-процентная продольная пульсация ломает не косметику, а
|
||||
все три величины, ради которых постановка и считается.
|
||||
|
||||
Отчёт печатает период моды, время её вязкого затухания и измеренную пульсацию в конце прогона,
|
||||
с явным предупреждением, если она превысила 5% от U.
|
||||
|
||||
**Инструмент разбора — `--xt`.** Каждые `--xt-every` шагов пишется срез ⟨ρ⟩ и u_x вдоль осевой
|
||||
линии, по строке на срез. Наклон полос на такой диаграмме прямо даёт скорость распространения:
|
||||
звук (±c_s) или конвекция (U). Стоячие узлы видны как вертикальные линии постоянной фазы, и это
|
||||
сразу отличает резонанс от неустойчивости самого граничного условия, привязанной к столбцу x = 0
|
||||
и никуда не бегущей.
|
||||
|
||||
Есть и **губка после входа** — `--sponge-in <столбцов>`, по умолчанию выключена. Гасит продольные
|
||||
волны до того, как они отразятся от входа-поршня. Осмысленна на высоких Re, где стартовая волна
|
||||
перестаёт быть безобидной полоской; цена — искажение профиля прямо на входе, поэтому включать её
|
||||
надо осознанно, а не «на всякий случай».
|
||||
|
||||
Что **не помогает** и оставлено только для повторной проверки — `--init-taper`. Замерено:
|
||||
сглаживание стартовой скорости у тела давит возмущение плотности на первом шаге восьмикратно
|
||||
(2.54·10⁻² → 3.13·10⁻³), но пик ЗА ПРОГОН при этом даже подрастает (до 3.22·10⁻²). Возмущение
|
||||
просто переносится во времени: поток всё равно обязан разогнаться вокруг тела, и энергия этого
|
||||
переходного процесса задана физикой, а не гладкостью начального поля. Умолчание — 0.
|
||||
|
||||
## Валидационная кампания
|
||||
|
||||
`bench/` — 115 прогонов на ≈90 часов GPU, разложенных по девяти группам: эталоны первоисточников,
|
||||
цилиндр против литературы, модели стенки, профили крыла, сложная и множественная геометрия,
|
||||
границы домена, старт и время жизни, внутренние инварианты, сверхмелкие сетки до 4096×2048.
|
||||
Каждый прогон кладёт логи, ряды, машиночитаемую сводку и гифку на всю свою длительность в
|
||||
собственную папку.
|
||||
|
||||
```sh
|
||||
cd bench
|
||||
python preflight.py # каждый сценарий стартует на два шага: ловит опечатки
|
||||
./run_campaign.sh --calibrate # замерить MLUPS этой машины: оценки в часах иначе гадание
|
||||
./run_campaign.sh --dry-run # смета: что, сколько шагов, сколько часов
|
||||
./run_campaign.sh --resume # считать, пропуская уже готовое
|
||||
```
|
||||
|
||||
Предполётную проверку стоит гонять всерьёз: она поймала, что вся группа сверхмелких сеток
|
||||
падала на пределе GPU (см. ниже), а девять прогонов передавали `--body-x` дважды. Оба отказа
|
||||
проявились бы только на сервере, часов через двадцать после старта кампании.
|
||||
|
||||
Подробности — в `bench/README.md`: раскладка выходных файлов, таблица групп, модель стоимости и
|
||||
список того, что известно заранее (какие прогоны обязаны развалиться и почему).
|
||||
|
||||
## Развёртывание (Docker)
|
||||
|
||||
```sh
|
||||
docker build -t kbc2d docs/theory/2d_solver
|
||||
docker run --rm --gpus all kbc2d --calibrate # проверить, что GPU виден
|
||||
docker run -d --gpus all -v "$PWD/out:/work/bench/out" kbc2d --resume
|
||||
```
|
||||
|
||||
`ENTRYPOINT` — драйвер кампании, `CMD` по умолчанию `--dry-run`: случайный `docker run` покажет
|
||||
смету и выйдет, а не запустит сточасовую задачу.
|
||||
|
||||
**Главная тонкость — Vulkan внутри контейнера.** NVIDIA Container Toolkit подкладывает
|
||||
Vulkan-ICD (`nvidia_icd.json`) только если в `NVIDIA_DRIVER_CAPABILITIES` есть `graphics`;
|
||||
с одним `compute` wgpu не увидит ни одного адаптера. В образе это прописано, но может быть
|
||||
переопределено снаружи, поэтому `vulkan-tools` лежит внутрь: первым делом на сервере стоит
|
||||
выполнить `docker run --rm --gpus all --entrypoint vulkaninfo kbc2d --summary`.
|
||||
|
||||
Проверено локально: образ собирается, кампания внутри него проходит смоук с монтированием
|
||||
результатов на хост, физика совпадает с хостовой до последней цифры (ошибка Тейлора–Грина
|
||||
9.829e-3 при N=64 и 2.401e-3 при N=128 — те же значения, что вне контейнера), а `--backend gpu`
|
||||
без проброшенной карты отказывает явным сообщением, а не считает молча. GPU-путь в контейнере
|
||||
проверяется только на машине с картой.
|
||||
|
||||
## Дальше
|
||||
|
||||
- σ·n-кросс-чек силы (интеграл тензора напряжений по контуру) как независимая проверка GMEM;
|
||||
@@ -365,4 +521,7 @@ CPU (f64) и GPU (f32) на одной постановке совпадают
|
||||
стартовый импульс от появления тела в потоке;
|
||||
- подвижные и вращающиеся тела: GMEM уже записан в галилей-инвариантной форме и принимает
|
||||
скорость стенки на линке, но подача этой скорости не подключена;
|
||||
- несколько тел одновременно (сейчас — одно тело на выбор из семи форм).
|
||||
- разбор нерешённых 12% по Cd и предела устойчивости Re ≈ 5·10⁴ — обе задачи вынесены в
|
||||
кампанию (группы B и G), выводы делать по её данным;
|
||||
- больше четырёх тел в домене: сейчас сила считается по четырём вёдрам (`MAX_BODY_BUCKETS`),
|
||||
геометрия при этом собирается из любого числа тел, но силы сверх четвёртого сливаются вместе.
|
||||
|
||||
Reference in New Issue
Block a user