diff --git a/.gitignore b/.gitignore index aeecb12..3cd50d3 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,9 @@ target/ docs/theory/2d_solver/*.gif docs/theory/2d_solver/*.csv docs/theory/2d_solver/out/ +# Результаты валидационной кампании — гигабайты гифок и рядов. Сам список сценариев +# (bench/scenarios.json) лежит рядом с драйверами и версионируется. +docs/theory/2d_solver/bench/out/ # --------------------------------------------------------------------------- # Редакторы и ОС diff --git a/docs/theory/2d_solver/.dockerignore b/docs/theory/2d_solver/.dockerignore new file mode 100644 index 0000000..d603d6c --- /dev/null +++ b/docs/theory/2d_solver/.dockerignore @@ -0,0 +1,14 @@ +# Каталог сборки cargo — сотни мегабайт, внутри образа он собирается заново. +target/ +# Результаты прогонов: гифки и ряды монтируются с хоста, в образ не кладутся. +bench/out/ +*.gif +*.csv +*.json +!bench/scenarios.json +# Мусор редакторов и ОС +.vs/ +.vscode/ +.idea/ +.DS_Store +Thumbs.db diff --git a/docs/theory/2d_solver/Dockerfile b/docs/theory/2d_solver/Dockerfile new file mode 100644 index 0000000..f6a645e --- /dev/null +++ b/docs/theory/2d_solver/Dockerfile @@ -0,0 +1,51 @@ +# Образ для развёртывания решателя и валидационной кампании на сервере с NVIDIA. +# +# Сборка и запуск: +# 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" --name kbc2d kbc2d --resume +# +# ГЛАВНАЯ ТОНКОСТЬ — Vulkan в контейнере. NVIDIA Container Toolkit подкладывает внутрь +# Vulkan-ICD (nvidia_icd.json) только если в NVIDIA_DRIVER_CAPABILITIES есть `graphics`. +# С одним `compute` wgpu не увидит НИ ОДНОГО адаптера и решатель откажется стартовать с +# --backend gpu. Поэтому capabilities прописаны в образе, а vulkan-tools положен внутрь, +# чтобы первым делом можно было выполнить `vulkaninfo` и убедиться, что карта видна. + +# ── сборка ─────────────────────────────────────────────────────────────────── +# Версия тулчейна закреплена: та же, на которой решатель собирался и проверялся. +FROM rust:1.97-bookworm AS build +WORKDIR /src + +# Сначала только манифесты — тогда слой с зависимостями переиспользуется, пока они не менялись. +COPY Cargo.toml Cargo.lock ./ +RUN mkdir src && echo 'fn main() {}' > src/main.rs \ + && cargo build --release --locked 2>/dev/null || true \ + && rm -rf src + +COPY src ./src +# touch нужен, чтобы cargo не спутал новые исходники с заглушкой из слоя выше +RUN touch src/main.rs && cargo build --release --locked + +# ── исполнение ─────────────────────────────────────────────────────────────── +FROM debian:bookworm-slim +LABEL org.opencontainers.image.title="kbc2d" +LABEL org.opencontainers.image.description="Двумерный решатель LBM D2Q9 с энтропийным столкновением KBC" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libvulkan1 vulkan-tools python3 ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Без graphics в списке возможностей Vulkan-ICD внутрь не попадёт — см. шапку. +ENV NVIDIA_VISIBLE_DEVICES=all +ENV NVIDIA_DRIVER_CAPABILITIES=compute,utility,graphics + +WORKDIR /work +COPY --from=build /src/target/release/kbc2d /work/target/release/kbc2d +COPY bench /work/bench +RUN chmod +x /work/bench/run_campaign.sh + +# Кампания идёт десятки часов, поэтому по умолчанию образ НЕ запускает её: случайный +# `docker run` покажет смету и выйдет. +WORKDIR /work/bench +ENTRYPOINT ["./run_campaign.sh"] +CMD ["--dry-run"] diff --git a/docs/theory/2d_solver/README.md b/docs/theory/2d_solver/README.md index 210a19b..45acd7f 100644 --- a/docs/theory/2d_solver/README.md +++ b/docs/theory/2d_solver/README.md @@ -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`), + геометрия при этом собирается из любого числа тел, но силы сверх четвёртого сливаются вместе. diff --git a/docs/theory/2d_solver/bench/README.md b/docs/theory/2d_solver/bench/README.md new file mode 100644 index 0000000..61318b8 --- /dev/null +++ b/docs/theory/2d_solver/bench/README.md @@ -0,0 +1,113 @@ +# Валидационная кампания + +115 прогонов, ≈90 часов на RTX 4070 Ti. Проверяет решатель по трём независимым линиям: +эталонам из статей авторов метода, литературе по обтеканию тел и внутренним инвариантам самой +схемы. Каждый прогон кладёт логи, ряды, машиночитаемую сводку и гифку в собственную папку. + +## Быстрый старт на сервере + +```sh +docker build -t kbc2d docs/theory/2d_solver +docker run --rm --gpus all --entrypoint python3 kbc2d preflight.py # сценарии стартуют +docker run --rm --gpus all kbc2d --calibrate # GPU виден +docker run -d --gpus all -v "$PWD/out:/work/bench/out" --name kbc2d kbc2d --resume +docker logs -f kbc2d +``` + +Первым делом стоит убедиться, что Vulkan внутри контейнера действительно видит карту: + +```sh +docker run --rm --gpus all --entrypoint vulkaninfo kbc2d --summary | head -20 +``` + +Если адаптеров ноль — почти наверняка дело в `NVIDIA_DRIVER_CAPABILITIES`: Container Toolkit +подкладывает Vulkan-ICD только при наличии `graphics` в списке. В образе это прописано, но +может быть переопределено снаружи. + +## Без Docker + +```sh +cargo build --release # из docs/theory/2d_solver +cd bench +python preflight.py # каждый сценарий стартует на два шага +./run_campaign.sh --calibrate # замерить MLUPS этой машины +KBC2D_GPU_MLUPS=1400 ./run_campaign.sh --dry-run +./run_campaign.sh --resume +``` + +Под Windows то же самое делает `run_campaign.ps1` (тот же `scenarios.json`); он нужен для +локальной отладки обвязки, целевая машина — Linux. + +## Ключи драйвера + +| ключ | что делает | +|---|---| +| `--dry-run` | печатает смету: что, сколько шагов, сколько часов. Ничего не считает | +| `--calibrate` | три коротких прогона, замер фактических MLUPS этой машины | +| `--smoke` | три самых дешёвых прогона: проверить обвязку, а не физику | +| `--resume` | пропускает прогоны, у которых уже есть `summary.json` | +| `--group A,B` | только выбранные группы | +| `--only cyl_re150` | по подстроке идентификатора | +| `--budget-hours 24` | остановиться, когда время выйдет | + +Прогон, который упал или развалился, помечается в сводке и **не останавливает кампанию**: +группы E и G специально ищут предел устойчивости, там развал — ожидаемый результат. + +## Что где лежит + +``` +out/summary.csv сводная таблица: id, группа, статус, секунды, стоимость +out/campaign.log журнал с отметками времени +out//cmd.txt точная команда, которой прогон был запущен +out//log.txt полный вывод +out//report.txt только итоговый отчёт +out//series.csv временные ряды по шагам +out//summary.json ключевые метрики машиночитаемо — с этого удобно начинать разбор +out//*.gif анимация +out//*_case.csv энергия, энстрофия, палинстрофия (эталонные течения) +out//xt_*.csv x–t диаграммы (группа G) +``` + +## Группы + +| | прогонов | что проверяется | +|---|---|---| +| **A** | 12 | эталоны первоисточников: Тейлор–Грин (второй порядок сходимости; при фиксированном u₀ — полка O(Ma²)), сдвиговый слой Re=3·10⁴, затухающая турбулентность | +| **B** | 16 | цилиндр против литературы: стационар Re=20/40, дорожка Re=100…300, разрешение D=16…128, блокировка Ny/D=6…32 с экстраполяцией к бесконечной среде | +| **C** | 20 | модели стенки и субсеточность: hrr / grad / bouzidi / staircase при Re=150 и 2000, плюс сдвиг тела внутри клетки на 0 / ¼ / ½ для всех четырёх | +| **D** | 14 | профили крыла: поляра NACA 0012, Re-серия, изгиб 4412, сходимость по хорде | +| **E** | 12 | сложная и множественная геометрия: тандем, решётка, перфорация, многоэлементный профиль, сверхтонкая пластина, клин, зазубренная кромка | +| **F** | 9 | поле влияния и границы домена: отступы до входа и выхода, вложенный патч | +| **G** | 14 | старт, акустика, время жизни: x–t диаграммы, губки, предел по Re, прогон на 10⁷ шагов | +| **H** | 9 | инварианты: симметрия, зеркальность, зависимость от числа Маха, расхождение f32 против f64 | +| **I** | 9 | сверхмелкие сетки 4096×2048 по всем формам и композиции из трёх тел | + +## Стоимость и время + +Стоимость каждого прогона хранится в **обновлениях узлов** (`nodes_per_step × steps`) — это +единственная мера, переносимая между машинами. Часы драйвер получает, поделив её на MLUPS. +Оценки по умолчанию исходят из 1200 MLUPS на GPU и 22 на CPU; **`--calibrate` обязателен**, +потому что эти числа взяты с другой машины. + +Пересобрать список с другой длительностью: + +```sh +python gen_scenarios.py --scale 3 # все прогоны в полтора раза длиннее (≈135 ч) +``` + +## Что известно заранее + +- **Точность f32.** Замерено на Тейлоре–Грине: GPU совпадает с CPU/f64, пока истинная ошибка + выше ~10⁻³, и промахивается в 40 раз, когда она ниже. Поэтому исследования сходимости из + группы A идут на CPU — это указано в самих сценариях. +- **Развалы ожидаемы** в группе G (предел по Re) и у прогона `A09_shear_n512_lbgk`: LBGK при + Re=3·10⁴ обязан развалиться там, где KBC доживает — это и есть проверяемое утверждение. +- **Гифки** пишутся на всю длительность прогона, в реальном времени, 10 кадр/с. Число кадров + этим задано жёстко (у самого длинного прогона их 16 666), поэтому единственный рычаг — + размер кадра. Замерено: 0.103 байта на пиксель после LZW; отсюда бюджет + кадры×пиксели ≤ 1.5·10⁹ на гифку, но ширина не опускается ниже 480 пикселей. Итог по + кампании: **≈4.4 ГБ**, самый тяжёлый файл 226 МБ. +- **Перед запуском** имеет смысл прогнать предполётную проверку: каждый сценарий стартует на + два шага, что ловит опечатки в ключах и несовместимые сочетания до того, как кампания уйдёт + считать на девяносто часов. Именно она поймала, что вся группа I падала на пределе GPU в + 65535 рабочих групп на измерение. diff --git a/docs/theory/2d_solver/bench/gen_scenarios.py b/docs/theory/2d_solver/bench/gen_scenarios.py new file mode 100644 index 0000000..33b34fe --- /dev/null +++ b/docs/theory/2d_solver/bench/gen_scenarios.py @@ -0,0 +1,518 @@ +#!/usr/bin/env python3 +# Генератор списка прогонов кампании -> scenarios.json +# +# Список порождается кодом, а не правится руками: серии по Re, по разрешению и по углу атаки +# получаются циклами, а стоимость каждого прогона считается тут же и суммируется. Запуск: +# python gen_scenarios.py # перезаписать scenarios.json и напечатать смету +# python gen_scenarios.py --scale 2 # растянуть все длительности вдвое +# +# Стоимость меряется в ОБНОВЛЕНИЯХ УЗЛОВ (nodes_per_step * steps) — единственная переносимая +# между машинами мера. Часы драйвер получает из неё, поделив на фактические MLUPS, которые +# замеряет на месте (--calibrate). + +import argparse +import json +import math +import os + +RUNS = [] +U_LAT = 0.05 # решёточная скорость по умолчанию +DX = 0.1 # размер клетки, м (умолчание решателя) +U_PHYS = 30.0 # скорость потока, м/с (умолчание решателя) +GIF_MAX_W = 1280 # предельная ширина кадра после прореживания +GIF_MIN_W = 480 # ниже не опускаемся: нечитаемая гифка бесполезнее большой +GIF_PX_BUDGET = 1.5e9 # кадры×пиксели на гифку; при 0.103 байта на пиксель это ~150 МБ + + +def conv_steps(d_cells, n_conv, u_lat=U_LAT): + """Сколько шагов нужно на n_conv конвективных времён D/U.""" + return int(round(n_conv * d_cells / u_lat)) + + +def add(rid, group, title, expect, args, nodes, steps, backend="gpu", + gif=None, nx=None, extra_out=()): + """Записать прогон. `nodes` — обновлений узлов за шаг, `steps` — сколько шагов.""" + a = list(args) + ["--steps", str(steps), "--backend", backend] + # ряды прореживаем так, чтобы их осталось порядка 200 тысяч записей + if steps > 400_000: + a += ["--series-every", str(max(1, steps // 200_000))] + if "case_csv" in extra_out: + a += ["--case-csv", f"{rid}_case.csv"] + if gif: + # Гифка идёт на всю длительность прогона в реальном времени, 10 кадр/с — число кадров + # задано физикой прогона и не обсуждается. Единственный рычаг — пиксели в кадре, и он + # нужен: замерено 0.103 байта на пиксель после LZW, так что прогон на 16 тысяч кадров + # при 960×576 весит 900 МБ, а такой файл уже нечем открыть. Поэтому кроме ограничения + # по ширине действует бюджет ПРОИЗВЕДЕНИЯ кадры×пиксели, но ширина не опускается ниже + # GIF_MIN_W: нечитаемая гифка бесполезнее большой. + ny = max(1, int(nodes) // max(1, nx or 1)) + u_lat = float(a[a.index("--u-lat") + 1]) if "--u-lat" in a else U_LAT + frames = max(1, int(steps * (u_lat * DX / U_PHYS) * 10)) + down = max(1, math.ceil((nx or 0) / GIF_MAX_W)) + while nx and frames * (nx // down) * (ny // down) > GIF_PX_BUDGET \ + and nx // (down + 1) >= GIF_MIN_W: + down += 1 + a += ["--gif", gif, "--gif-field", "vorticity", "--gif-every", "auto", + "--gif-fps", "10", "--gif-speed", "1", + "--gif-downsample", str(down), "--gif-scale", "1"] + RUNS.append({ + "id": rid, "group": group, "title": title, "expect": expect, + "backend": backend, "args": a, + "nodes_per_step": int(nodes), "steps": int(steps), + "cost": int(nodes) * int(steps), + "outputs": list(extra_out), + }) + + +# ══════════════════════════════════════════════════════════════════════════════ +# A. Эталоны первоисточников: периодические течения, тела и ГУ нет вовсе +# ══════════════════════════════════════════════════════════════════════════════ +def group_a(scale): + NU = 0.0192 + for n in (64, 128, 256, 512): + # диффузионное измельчение: nu фиксирована, u0 ~ 1/N, поэтому Re сохраняется, а + # число Маха падает вместе с сеткой — только так виден второй порядок целиком + u0 = 0.03 * 64 / n + tc = int(round(math.log(2.0) / (NU * (2 * math.pi / n) ** 2 * 17))) + add(f"A{len(RUNS)+1:02d}_tg_diff_n{n}", "A", + f"Тейлор–Грин, диффузионное измельчение, N={n} (CPU/f64)", + "порядок сходимости 2 по ряду N=64..512; ошибка на N=512 ниже 2e-4", + ["--case", "taylor-green", "--nx", str(n), "--ny", str(n), "--refine", "1", + "--u-lat", f"{u0:.6f}", "--re", "100", "--case-every", str(max(1, tc // 20))], + n * n, tc, backend="cpu", extra_out=("case_csv",)) + + for n in (128, 256): + # при ФИКСИРОВАННОМ u0 ошибка упирается в полку O(Ma^2) и от сетки не зависит + tc = int(round(math.log(2.0) / (NU * (2 * math.pi / n) ** 2 * 17))) + add(f"A{len(RUNS)+1:02d}_tg_fixed_n{n}", "A", + f"Тейлор–Грин при фиксированном u₀, N={n} — демонстрация полки O(Ma²)", + "ошибка перестаёт падать с ростом N: это свойство слабо-сжимаемого метода", + ["--case", "taylor-green", "--nx", str(n), "--ny", str(n), "--refine", "1", + "--u-lat", "0.03", "--re", f"{0.03 * n / NU:.1f}", + "--case-every", str(max(1, tc // 20))], + n * n, tc, backend="cpu", extra_out=("case_csv",)) + + for n in (512,): + for op, km, tag in (("kbc", "n1", "kbc_n1"), ("kbc", "n2", "kbc_n2"), ("bgk", "n1", "lbgk")): + steps = int(4 * n / 0.04 * scale) + add(f"A{len(RUNS)+1:02d}_shear_n{n}_{tag}", "A", + f"Сдвиговый слой Re=3·10⁴, N={n}, {tag}", + "KBC доживает до конца, LBGK обязан развалиться (разд. VII статьи)", + ["--case", "shear-layer", "--nx", str(n), "--ny", str(n), "--refine", "1", + "--u-lat", "0.04", "--re", "30000", "--collision", op, "--kbc-model", km, + "--case-every", str(max(1, steps // 200))], + n * n, steps, gif=f"shear_{tag}.gif", nx=n, extra_out=("case_csv",)) + + for n, tag, ops in ((2048, "n2048", ("kbc", "bgk")), (4096, "n4096", ("kbc",))): + for op in ops: + steps = int(200_000 * scale * (2048 / n)) + add(f"A{len(RUNS)+1:02d}_turb_{tag}_{op}", "A", + f"Затухающая турбулентность N={n}, {op}", + "энстрофия и палинстрофия падают монотонно; KBC держится там, где LBGK нет", + ["--case", "decaying-turbulence", "--nx", str(n), "--ny", str(n), "--refine", "1", + "--u-lat", "0.02", "--re", f"{0.02 * n / 8.16e-5:.0f}", "--collision", op, + "--case-every", str(max(1, steps // 300))], + n * n, steps, gif=f"turb_{tag}_{op}.gif", nx=n, extra_out=("case_csv",)) + + +# ══════════════════════════════════════════════════════════════════════════════ +# B. Цилиндр против литературы +# ══════════════════════════════════════════════════════════════════════════════ +def flatten(d): + """Словарь ключей в плоский argv. Именно словарь, а не список: clap отвергает + повторённый ключ, а сценарии сплошь и рядом переопределяют умолчания сборщика.""" + a = [] + for k, v in d.items(): + a += [k, str(v)] + return a + + +def cyl_args(d, re, nx, ny, **kw): + a = {"--shape": "cylinder", "--size": d, "--nx": nx, "--ny": ny, + "--body-x": nx // 5, "--re": re, "--refine": 1} + a.update({"--" + k.replace("_", "-"): v for k, v in kw.items()}) + return flatten(a) + + +def group_b(scale): + # стационарные режимы: дорожки нет, Cd — одно число + for re in (20, 40): + d, nx, ny = 48, 960, 576 + st = int(conv_steps(d, 60) * scale) + add(f"B{len(RUNS)-len(RUNS)+len([r for r in RUNS if r['group']=='B'])+1:02d}_cyl_re{re}", + "B", f"Цилиндр Re={re}, стационар, D=48", + "Cd выходит на постоянную; сравнение с литературой по стационарному обтеканию", + cyl_args(d, re, nx, ny, pert_amp=0), nx * ny, st, + gif=f"cyl_re{re}.gif", nx=nx) + + # дорожка Кармана: длинное осреднение, это главный заход на литературу + for re in (100, 150, 200, 300): + d, nx, ny = 64, 1280, 768 + st = int(conv_steps(d, 1500) * scale) + add(f"B{len([r for r in RUNS if r['group']=='B'])+1:02d}_cyl_re{re}", "B", + f"Цилиндр Re={re}, дорожка Кармана, D=64, 1500 конв. времён", + "St≈0.183 и ⟨Cd⟩≈1.33 при Re=150 после поправки на блокировку", + cyl_args(d, re, nx, ny), nx * ny, st, gif=f"cyl_re{re}.gif", nx=nx) + + # разрешение тела: сходимость Cd + for d in (16, 32, 64, 128): + nx, ny = 20 * d, 12 * d + st = int(conv_steps(d, 800) * scale) + add(f"B{len([r for r in RUNS if r['group']=='B'])+1:02d}_cyl_d{d}", "B", + f"Цилиндр Re=150, разрешение D={d}", + "Cd сходится по D; экстраполяция даёт сеточно-независимое значение", + cyl_args(d, 150, nx, ny), nx * ny, st, gif=f"cyl_d{d}.gif", nx=nx) + + # блокировка канала: экстраполяция к бесконечной среде — прямой заход на нерешённые 12% + for k in (6, 8, 12, 16, 24, 32): + d, nx, ny = 48, 960, k * 48 + st = int(conv_steps(d, 1000) * scale) + add(f"B{len([r for r in RUNS if r['group']=='B'])+1:02d}_cyl_block{k}", "B", + f"Цилиндр Re=150, блокировка Ny/D={k}", + "экстраполяция Cd и St к нулевой блокировке; ожидание — сходимость к 1.33 и 0.183", + cyl_args(d, 150, nx, ny), nx * ny, st, gif=None, nx=nx) + + +# ══════════════════════════════════════════════════════════════════════════════ +# C. Модели стенки и субсеточность +# ══════════════════════════════════════════════════════════════════════════════ +def group_c(scale): + for wall in ("hrr", "grad", "bouzidi", "staircase"): + for re in (150, 2000): + d, nx, ny = 32, 640, 384 + st = int(conv_steps(d, 400 if re > 20 else 60) * scale) + add(f"C{len([r for r in RUNS if r['group']=='C'])+1:02d}_wall_{wall}_re{re}", "C", + f"Модель стенки {wall}, Re={re}", + "все субсеточные модели обязаны сойтись к одному пределу; staircase — база", + cyl_args(d, re, nx, ny, wall=wall), nx * ny, st, + gif=f"wall_{wall}_re{re}.gif" if re == 150 else None, nx=nx) + + # Чувствительность к положению тела ВНУТРИ клетки — прямая проверка субсеточности, и + # единственное измерение, которое вообще разделяет модели стенки. Двух положений для этого + # мало: по двум точкам не отличить систематический разброс от совпадения, поэтому берём + # четверти, и все четыре модели. Прогоны короткие (стационар при Re=20), вся серия — минуты. + for wall in ("hrr", "grad", "bouzidi", "staircase"): + for off in (0.0, 0.25, 0.5): + d, nx, ny = 16, 320, 192 + st = int(conv_steps(d, 60) * scale) + add(f"C{len([r for r in RUNS if r['group']=='C'])+1:02d}_sub_{wall}_{int(off*100):02d}", + "C", f"Субклеточный сдвиг тела {off} клетки, стенка {wall}", + "разброс Cd по сдвигам: чем субсеточнее модель, тем он меньше", + cyl_args(d, 20, nx, ny, body_y=96 + off, wall=wall, pert_amp=0), + nx * ny, st) + + +# ══════════════════════════════════════════════════════════════════════════════ +# D. Профили крыла +# ══════════════════════════════════════════════════════════════════════════════ +def foil_args(chord, alpha, re, nx, ny, naca="0012", **kw): + a = {"--shape": "naca", "--naca": naca, "--size": chord, + "--body-angle": alpha, "--nx": nx, "--ny": ny, + "--body-x": nx // 4, "--re": re, "--refine": 1} + a.update({"--" + k.replace("_", "-"): v for k, v in kw.items()}) + return flatten(a) + + +def group_d(scale): + # поляра: наклон Cl(alpha) и положение сваливания + for al in (0, 4, 8, 12, 16): + c, nx, ny = 128, 2048, 1024 + st = int(conv_steps(c, 400) * scale) + add(f"D{len([r for r in RUNS if r['group']=='D'])+1:02d}_naca0012_a{al:02d}", "D", + f"NACA 0012, α={al}°, Re=1000, хорда 128", + "линейный участок Cl(α) и срыв; сравнение с низкорейнольдсовой литературой", + foil_args(c, al, 1000, nx, ny), nx * ny, st, + gif=f"naca0012_a{al:02d}.gif", nx=nx) + + for re in (500, 2000, 10000): + c, nx, ny = 128, 2048, 1024 + st = int(conv_steps(c, 400) * scale) + add(f"D{len([r for r in RUNS if r['group']=='D'])+1:02d}_naca0012_re{re}", "D", + f"NACA 0012, α=8°, Re={re}", + "перестройка следа с ростом Re; проверка устойчивости на профиле", + foil_args(c, 8, re, nx, ny), nx * ny, st, + gif=f"naca0012_re{re}.gif", nx=nx) + + for al in (0, 6, 12): + c, nx, ny = 128, 2048, 1024 + st = int(conv_steps(c, 400) * scale) + add(f"D{len([r for r in RUNS if r['group']=='D'])+1:02d}_naca4412_a{al:02d}", "D", + f"NACA 4412 (с изгибом), α={al}°, Re=1000", + "ненулевой Cl при α=0 — прямая проверка средней линии профиля", + foil_args(c, al, 1000, nx, ny, naca="4412"), nx * ny, st, + gif=f"naca4412_a{al:02d}.gif", nx=nx) + + for c in (48, 96, 192): + nx, ny = 16 * c, 8 * c + st = int(conv_steps(c, 300) * scale) + add(f"D{len([r for r in RUNS if r['group']=='D'])+1:02d}_naca_chord{c}", "D", + f"NACA 0012, α=8°, сходимость по хорде {c}", + "Cl и Cd сходятся по разрешению хорды", + foil_args(c, 8, 1000, nx, ny), nx * ny, st, nx=nx) + + +# ══════════════════════════════════════════════════════════════════════════════ +# E. Сложная и множественная геометрия +# ══════════════════════════════════════════════════════════════════════════════ +def group_e(scale): + # тандем: литература даёт переключение режимов около L/D 3.5–4 + for ld in (1.5, 3.0, 5.0): + d, nx, ny = 48, 1440, 720 + gap = int(ld * d) + bodies = f"cylinder:d={d},x=360,y=360; cylinder:d={d},x={360 + gap},y=360" + st = int(conv_steps(d, 800) * scale) + add(f"E{len([r for r in RUNS if r['group']=='E'])+1:02d}_tandem_ld{int(ld*10)}", "E", + f"Тандем цилиндров, зазор L/D={ld}", + "при малом зазоре у заднего тела ОТРИЦАТЕЛЬНОЕ сопротивление; переход около 3.5–4", + ["--bodies", bodies, "--nx", str(nx), "--ny", str(ny), "--re", "150", "--refine", "1"], + nx * ny, st, gif=f"tandem_ld{int(ld*10)}.gif", nx=nx) + + # решётка как пористая среда + for step_d, tag in ((3, "sparse"), (2, "dense")): + d, nx, ny = 24, 1440, 720 + bodies = "; ".join( + f"cylinder:d={d},x={360 + i * step_d * d},y={360 + (j - 1.5) * step_d * d}" + for i in range(4) for j in range(4)) + st = int(conv_steps(d, 600) * scale) + add(f"E{len([r for r in RUNS if r['group']=='E'])+1:02d}_array_{tag}", "E", + f"Решётка 4×4 цилиндров, шаг {step_d}D — пористая среда", + "суммарное сопротивление и структура течения сквозь набор тел", + ["--bodies", bodies, "--nx", str(nx), "--ny", str(ny), "--re", "150", "--refine", "1"], + nx * ny, st, gif=f"array_{tag}.gif", nx=nx) + + # перфорированная пластина: набор коротких пластин со щелями + for open_frac, tag in ((0.2, "open20"), (0.5, "open50")): + nx, ny = 1200, 600 + seg, gap = 40, int(40 * open_frac / (1 - open_frac)) + bodies = "; ".join( + f"plate:d={seg},t=0.12,a=90,x=300,y={60 + k * (seg + gap)}" + for k in range(max(1, (ny - 120) // (seg + gap)))) + st = int(conv_steps(seg, 500) * scale) + add(f"E{len([r for r in RUNS if r['group']=='E'])+1:02d}_perf_{tag}", "E", + f"Перфорированная пластина, скважность {open_frac}", + "струи в щелях и общее сопротивление; проверка множества тонких тел", + ["--bodies", bodies, "--nx", str(nx), "--ny", str(ny), "--re", "500", "--refine", "1"], + nx * ny, st, gif=f"perf_{tag}.gif", nx=nx) + + # многоэлементный профиль: основной + предкрылок + nx, ny = 2048, 1024 + bodies = ("naca:naca=4412,d=200,a=6,x=560,y=512; " + "naca:naca=2412,d=70,a=18,x=430,y=470") + st = int(conv_steps(200, 300) * scale) + add(f"E{len([r for r in RUNS if r['group']=='E'])+1:02d}_multielem", "E", + "Многоэлементный профиль: основной 4412 плюс предкрылок", + "щель между элементами и раздельные Cl/Cd по телам", + ["--bodies", bodies, "--nx", str(nx), "--ny", str(ny), "--re", "2000", "--refine", "1"], + nx * ny, st, gif="multielem.gif", nx=nx) + + # сверхтонкая пластина: предел субсеточности, тело тоньше клетки + for t, tag in ((0.02, "t05"), (0.01, "t025")): + nx, ny = 1200, 600 + st = int(conv_steps(50, 500) * scale) + add(f"E{len([r for r in RUNS if r['group']=='E'])+1:02d}_thin_{tag}", "E", + f"Сверхтонкая пластина, толщина {t*50:.2f} клетки", + "предел субсеточной границы: тело тоньше клетки; ждём откаты на простой отскок", + ["--shape", "plate", "--size", "50", "--thickness", str(t), "--body-angle", "20", + "--nx", str(nx), "--ny", str(ny), "--re", "500", "--refine", "1"], + nx * ny, st, gif=f"thin_{tag}.gif", nx=nx) + + # клин и зазубренная кромка — субклеточная деталь на многоугольнике + nx, ny = 1200, 600 + st = int(conv_steps(60, 500) * scale) + add(f"E{len([r for r in RUNS if r['group']=='E'])+1:02d}_wedge", "E", + "Клин остриём против потока", "острая кромка: особая точка геометрии", + ["--shape", "polygon", "--poly=-30,-18;36,0;-30,18", "--size", "60", + "--nx", str(nx), "--ny", str(ny), "--re", "500", "--refine", "1"], + nx * ny, st, gif="wedge.gif", nx=nx) + saw = ";".join(f"{-30 + 12 * k},{(-1) ** k * 6}" for k in range(6)) + ";30,-18;-30,-18" + add(f"E{len([r for r in RUNS if r['group']=='E'])+1:02d}_serrated", "E", + "Зазубренная задняя кромка", "субклеточные зубцы: как их видит граница", + ["--shape", "polygon", f"--poly={saw}", "--size", "60", + "--nx", str(nx), "--ny", str(ny), "--re", "500", "--refine", "1"], + nx * ny, st, gif="serrated.gif", nx=nx) + + +# ══════════════════════════════════════════════════════════════════════════════ +# F. Поле влияния и границы домена +# ══════════════════════════════════════════════════════════════════════════════ +def group_f(scale): + d = 48 + for up in (2, 6, 16): + nx, ny = int((up + 20) * d), 12 * d + st = int(conv_steps(d, 600) * scale) + add(f"F{len([r for r in RUNS if r['group']=='F'])+1:02d}_inlet{up}d", "F", + f"Отступ до входа {up}D", + "с какого отступа результат перестаёт зависеть от положения входа", + cyl_args(d, 150, nx, ny, body_x=up * d), nx * ny, st) + for down in (5, 15, 40): + nx, ny = int((6 + down) * d), 12 * d + st = int(conv_steps(d, 600) * scale) + add(f"F{len([r for r in RUNS if r['group']=='F'])+1:02d}_outlet{down}d", "F", + f"Отступ до выхода {down}D", + "с какой длины следа выход перестаёт влиять на силы", + cyl_args(d, 150, nx, ny, body_x=6 * d), nx * ny, st) + for r in (1, 2, 3): + d, nx, ny = 32, 640, 384 + st = int(conv_steps(d, 600) * scale) + nodes = nx * ny + if r > 1: + ax, bx = int(nx // 5 - 1.5 * d), int(nx // 5 + 6.25 * d) + ay, by = int(ny / 2 - 2 * d), int(ny / 2 + 2 * d) + nodes += r * (r * (bx - ax) + 1) * (r * (by - ay) + 1) + add(f"F{len([r_ for r_ in RUNS if r_['group']=='F'])+1:02d}_refine{r}", "F", + f"Вложенный патч ×{r}", + "вносит ли связка уровней систематику: Cd и St обязаны совпасть", + cyl_args(d, 150, nx, ny, refine=r), nodes, st) + + +# ══════════════════════════════════════════════════════════════════════════════ +# G. Старт, акустика, время жизни +# ══════════════════════════════════════════════════════════════════════════════ +def group_g(scale): + d, nx, ny = 48, 960, 576 + for re in (150, 2000, 20000): + for init in ("uniform", "rest"): + st = int(conv_steps(d, 300) * scale) + add(f"G{len([r for r in RUNS if r['group']=='G'])+1:02d}_xt_re{re}_{init}", "G", + f"x–t диаграмма, Re={re}, старт {init}", + "откуда идёт стартовое возмущение и раскачивается ли продольная мода", + cyl_args(d, re, nx, ny, init=init, sponge_len=0, + xt=f"xt_re{re}_{init}.csv", xt_every=200), + nx * ny, st, gif=f"xt_re{re}_{init}.gif", nx=nx, extra_out=("xt",)) + for sp in (0, 40): + st = int(conv_steps(d, 400) * scale) + add(f"G{len([r for r in RUNS if r['group']=='G'])+1:02d}_sponge{sp}", "G", + f"Губка выхода {sp} столбцов при Re=5000", + "губка — единственное, что реально ест продольную моду", + cyl_args(d, 5000, nx, ny, sponge_len=sp), nx * ny, st) + for sp in (0, 40): + st = int(conv_steps(d, 400) * scale) + add(f"G{len([r for r in RUNS if r['group']=='G'])+1:02d}_spongein{sp}", "G", + f"Губка входа {sp} столбцов при Re=5000", + "помогает ли гасить волну до отражения от входа", + cyl_args(d, 5000, nx, ny, sponge_in=sp), nx * ny, st) + for re in (10000, 50000, 200000): + st = int(conv_steps(d, 200) * scale) + add(f"G{len([r for r in RUNS if r['group']=='G'])+1:02d}_limit_re{re}", "G", + f"Предел устойчивости, Re={re}", + "где именно гибнет счёт; часть этих прогонов обязана развалиться", + cyl_args(d, re, nx, ny), nx * ny, st) + # тест на время жизни: один очень длинный прогон + st = int(5_000_000 * scale) + add(f"G{len([r for r in RUNS if r['group']=='G'])+1:02d}_lifetime", "G", + "Тест на время жизни: 5·10⁶ шагов", + "не уплывает ли ⟨ρ⟩ и не деградирует ли решение на очень длинной дистанции", + cyl_args(d, 150, nx, ny), nx * ny, st, gif="lifetime.gif", nx=nx) + + +# ══════════════════════════════════════════════════════════════════════════════ +# H. Инварианты и точность +# ══════════════════════════════════════════════════════════════════════════════ +def group_h(scale): + d, nx, ny = 32, 640, 384 + st = int(conv_steps(d, 400) * scale) + add(f"H{len([r for r in RUNS if r['group']=='H'])+1:02d}_sym_cyl", "H", + "Симметрия: цилиндр при α=0 без возмущения", + "Cl и Cm обязаны остаться ≈0 — это внутреннее свойство схемы, не литература", + cyl_args(d, 40, nx, ny, pert_amp=0), nx * ny, st) + add(f"H{len([r for r in RUNS if r['group']=='H'])+1:02d}_sym_foil", "H", + "Симметрия: NACA 0012 при α=0 без возмущения", + "Cl ≈ 0 у симметричного профиля под нулевым углом", + foil_args(64, 0, 500, 1024, 512, pert_amp=0), 1024 * 512, + int(conv_steps(64, 300) * scale)) + for al, tag in ((8, "plus"), (-8, "minus")): + add(f"H{len([r for r in RUNS if r['group']=='H'])+1:02d}_mirror_{tag}", "H", + f"Зеркальность: NACA 0012 при α={al}°", + "ряды обязаны зеркалиться: Cl меняет знак, Cd совпадает", + foil_args(64, al, 500, 1024, 512), 1024 * 512, + int(conv_steps(64, 300) * scale)) + for ul in (0.02, 0.05, 0.1): + st2 = int(conv_steps(d, 400, ul) * scale) + add(f"H{len([r for r in RUNS if r['group']=='H'])+1:02d}_mach{int(ul*100):02d}", "H", + f"Зависимость от числа Маха, u_lat={ul}", + "результат обязан сходиться при Ma→0; расхождение и есть сжимаемостная ошибка", + cyl_args(d, 150, nx, ny, u_lat=ul), nx * ny, st2) + # прямой замер расхождения f32 и f64 от длины прогона + for bk in ("cpu", "gpu"): + add(f"H{len([r for r in RUNS if r['group']=='H'])+1:02d}_prec_{bk}", "H", + f"Точность: одинаковая постановка на {bk}", + "расхождение f32 против f64 в зависимости от длины прогона", + cyl_args(d, 150, nx, ny), nx * ny, int(conv_steps(d, 800) * scale), backend=bk) + + +# ══════════════════════════════════════════════════════════════════════════════ +# I. Сверхмелкие сетки по всем формам +# ══════════════════════════════════════════════════════════════════════════════ +def group_i(scale): + shapes = [ + ("cylinder", ["--shape", "cylinder"]), + ("square", ["--shape", "square"]), + ("diamond", ["--shape", "diamond"]), + ("ellipse", ["--shape", "ellipse", "--thickness", "0.35"]), + ("naca", ["--shape", "naca", "--naca", "0012", "--body-angle", "8"]), + ("triangle", ["--shape", "triangle"]), + ("plate", ["--shape", "plate", "--thickness", "0.08", "--body-angle", "25"]), + ("wedge", ["--shape", "polygon", "--poly=-128,-72;154,0;-128,72"]), + ] + d, nx, ny = 256, 4096, 2048 + st = int(conv_steps(d, 300) * scale) + for tag, extra in shapes: + add(f"I{len([r for r in RUNS if r['group']=='I'])+1:02d}_fine_{tag}", "I", + f"Сверхмелкая сетка 4096×2048, {tag}, D=256", + "держится ли субсеточная граница на предельном разрешении для этой формы", + extra + ["--size", str(d), "--nx", str(nx), "--ny", str(ny), + "--body-x", "820", "--re", "1000", "--refine", "1"], + nx * ny, st, gif=f"fine_{tag}.gif", nx=nx) + bodies = ("cylinder:d=200,x=800,y=1024; cylinder:d=200,x=1400,y=1024; " + "naca:naca=4412,d=300,a=10,x=2200,y=1024") + add(f"I{len([r for r in RUNS if r['group']=='I'])+1:02d}_fine_multi", "I", + "Сверхмелкая сетка, композиция из трёх тел", + "взаимодействие следов на предельном разрешении", + ["--bodies", bodies, "--nx", str(nx), "--ny", str(ny), "--re", "1000", "--refine", "1"], + nx * ny, st, gif="fine_multi.gif", nx=nx) + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--scale", type=float, default=2.0, + help="общий множитель длительности всех прогонов") + ap.add_argument("--out", default=os.path.join(os.path.dirname(__file__), "scenarios.json")) + args = ap.parse_args() + + for g in (group_a, group_b, group_c, group_d, group_e, group_f, group_g, group_h, group_i): + g(args.scale) + + doc = { + "meta": { + "note": "Стоимость в обновлениях узлов — единственная переносимая между машинами " + "мера. Часы драйвер получает, поделив её на фактические MLUPS, которые " + "замеряет на месте (--calibrate).", + "gif_policy": "вся длительность прогона, реальная скорость, 10 кадр/с; кадр " + "прореживается до ширины не больше 1280 пикселей", + "scale": args.scale, + "assumed_gpu_mlups": 1200, + "assumed_cpu_mlups": 22, + }, + "runs": RUNS, + } + with open(args.out, "w", encoding="utf-8") as f: + json.dump(doc, f, ensure_ascii=False, indent=1) + + print(f"прогонов: {len(RUNS)} файл: {args.out}\n") + print(f"{'гр':>3}{'прогонов':>10}{'обновлений узлов':>20}{'часов GPU':>12}{'часов CPU':>12}") + tot_gpu = tot_cpu = 0.0 + for g in sorted({r["group"] for r in RUNS}): + rs = [r for r in RUNS if r["group"] == g] + cost = sum(r["cost"] for r in rs) + hg = sum(r["cost"] / 1.2e9 / 3600 for r in rs if r["backend"] == "gpu") + hc = sum(r["cost"] / 2.2e7 / 3600 for r in rs if r["backend"] == "cpu") + tot_gpu += hg + tot_cpu += hc + print(f"{g:>3}{len(rs):>10}{cost:>20.3e}{hg:>12.1f}{hc:>12.1f}") + print(f"{'—':>3}{len(RUNS):>10}{sum(r['cost'] for r in RUNS):>20.3e}" + f"{tot_gpu:>12.1f}{tot_cpu:>12.1f}") + print(f"\nвсего ≈ {tot_gpu + tot_cpu:.1f} ч при 1200 MLUPS на GPU и 22 на CPU") + + +if __name__ == "__main__": + main() diff --git a/docs/theory/2d_solver/bench/preflight.py b/docs/theory/2d_solver/bench/preflight.py new file mode 100644 index 0000000..e07e023 --- /dev/null +++ b/docs/theory/2d_solver/bench/preflight.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# Предполётная проверка кампании: каждый сценарий запускается на два шага. +# +# python preflight.py # проверить все +# python preflight.py --group I # только группу +# +# Ловит опечатки в ключах, несовместимые сочетания и пределы железа ДО того, как кампания +# уйдёт считать на девяносто часов. Не бесплатная привычка, а окупившаяся: именно так +# выяснилось, что вся группа сверхмелких сеток падала на пределе GPU в 65535 рабочих групп +# на измерение, а девять прогонов группы F передавали --body-x дважды. +# +# Физику проверка не трогает вовсе: два шага — это ровно «запустилось и не упало». + +import argparse +import json +import os +import subprocess +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +BIN = os.environ.get("KBC2D_BIN") or os.path.join( + HERE, "..", "target", "release", "kbc2d" + (".exe" if os.name == "nt" else "")) + +# Всё, что пишет файлы, выкидываем: проверяем запуск, а не вывод. Ключ идёт со значением, +# поэтому следующий за ним аргумент тоже пропускается. +DROP = {"--gif", "--xt", "--case-csv", "--gif-field", "--gif-downsample", "--gif-scale", + "--gif-every", "--gif-fps", "--series-every"} + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--group", help="только эта группа, например I") + ap.add_argument("--scenarios", default=os.path.join(HERE, "scenarios.json")) + args = ap.parse_args() + + runs = json.load(open(args.scenarios, encoding="utf-8"))["runs"] + if args.group: + runs = [r for r in runs if r["group"].upper() == args.group.upper()] + if not runs: + print("под выборку не попал ни один прогон") + return 0 + if not os.path.exists(BIN): + print(f"не найден бинарь решателя: {BIN}") + return 1 + + bad = [] + for i, r in enumerate(runs, 1): + argv, skip = [], False + for a in r["args"]: + if skip: + skip = False + continue + if a in DROP: + skip = True + continue + argv.append(a) + if "--steps" in argv: + argv[argv.index("--steps") + 1] = "2" + else: + argv += ["--steps", "2"] + + p = subprocess.run([BIN] + argv + ["--verbose", "quiet", "--report-every", "0"], + capture_output=True, cwd=HERE) + ok = p.returncode == 0 + print("." if ok else "X", end="", flush=True) + if not ok: + tail = (p.stdout + p.stderr).decode("utf-8", "replace").strip().splitlines() + bad.append((r["id"], tail[-1] if tail else "(без вывода)")) + if i % 40 == 0: + print(f" {i}/{len(runs)}", flush=True) + + print(f"\n\nпроверено сценариев: {len(runs)}, не запустились: {len(bad)}") + for id_, msg in bad: + print(f" {id_:<28} {msg}") + return 1 if bad else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/theory/2d_solver/bench/run_campaign.ps1 b/docs/theory/2d_solver/bench/run_campaign.ps1 new file mode 100644 index 0000000..8e0e28e --- /dev/null +++ b/docs/theory/2d_solver/bench/run_campaign.ps1 @@ -0,0 +1,108 @@ +# Драйвер валидационной кампании для Windows. Делает ровно то же, что run_campaign.sh, и +# читает тот же scenarios.json — целевая машина Linux, а этот вариант нужен для локальной +# отладки обвязки. +# +# .\run_campaign.ps1 -DryRun +# .\run_campaign.ps1 -Calibrate +# .\run_campaign.ps1 -Smoke +# .\run_campaign.ps1 -Resume -Group A,B +# .\run_campaign.ps1 -Only cyl_re150 -BudgetHours 6 + +[CmdletBinding()] +param( + [switch]$DryRun, + [switch]$Resume, + [switch]$Calibrate, + [switch]$Smoke, + [string[]]$Group, + [string]$Only, + [double]$BudgetHours = 0, + [string]$Bin = "..\target\release\kbc2d.exe", + [string]$Scenarios = "scenarios.json", + [string]$Out = "out", + [double]$GpuMlups = 1200, + [double]$CpuMlups = 22 +) + +Set-Location $PSScriptRoot +if (-not (Test-Path $Bin)) { throw "не найден бинарь решателя: $Bin (собери cargo build --release)" } +if (-not (Test-Path $Scenarios)) { throw "не найден список сценариев: $Scenarios" } + +if ($Calibrate) { + "Калибровка на этой машине (три коротких прогона)…" + foreach ($c in @(@(240,120,'gpu'), @(960,480,'gpu'), @(480,240,'cpu'))) { + $o = & $Bin --nx $c[0] --ny $c[1] --size 16 --refine 1 --steps 3000 ` + --report-every 3000 --verbose full --backend $c[2] 2>$null + $m = ($o | Select-String 'MLUPS' | Select-Object -Last 1) + " $($c[0])x$($c[1]) на $($c[2]): $(if ($m) { ($m -replace '.*?([0-9.]+) MLUPS.*','$1') } else { 'не измерено' })" + } + "" + "Подставь замеренное: .\run_campaign.ps1 -DryRun -GpuMlups <число> -CpuMlups <число>" + return +} + +$runs = (Get-Content $Scenarios -Raw -Encoding UTF8 | ConvertFrom-Json).runs +if ($Group) { $g = $Group | ForEach-Object { $_.ToUpper() }; $runs = $runs | Where-Object { $g -contains $_.group.ToUpper() } } +if ($Only) { $runs = $runs | Where-Object { $_.id -like "*$Only*" } } +if ($Smoke) { $runs = $runs | Sort-Object cost | Select-Object -First 3 } +if (-not $runs) { "под выборку не попал ни один прогон"; return } + +$totalCost = ($runs | Measure-Object -Property cost -Sum).Sum +$estH = $totalCost / ($GpuMlups * 1e6) / 3600 +"прогонов: $($runs.Count) обновлений узлов: {0:e3} ≈ {1:F1} ч при $GpuMlups MLUPS" -f $totalCost, $estH +"результаты: $Out\\" +"" + +if ($DryRun) { + "{0,-28} {1,-3} {2,-4} {3,12} {4,8} {5}" -f 'id','гр','бэк','шагов','часов','название' + foreach ($r in $runs) { + $m = if ($r.backend -eq 'cpu') { $CpuMlups } else { $GpuMlups } + "{0,-28} {1,-3} {2,-4} {3,12} {4,8:F2} {5}" -f $r.id, $r.group, $r.backend, $r.steps, ($r.cost/($m*1e6)/3600), $r.title + } + return +} + +New-Item -ItemType Directory -Force $Out | Out-Null +$summary = Join-Path $Out 'summary.csv' +$log = Join-Path $Out 'campaign.log' +if (-not (Test-Path $summary)) { 'id,group,backend,status,seconds,steps,cost,title' | Out-File $summary -Encoding utf8 } + +$started = Get-Date +foreach ($r in $runs) { + $dir = Join-Path $Out $r.id + if ($Resume -and (Test-Path (Join-Path $dir 'summary.json'))) { "· $($r.id) — уже посчитан, пропускаю"; continue } + if ($BudgetHours -gt 0 -and ((Get-Date) - $started).TotalHours -ge $BudgetHours) { + "бюджет $BudgetHours ч исчерпан, останавливаюсь"; break + } + New-Item -ItemType Directory -Force $dir | Out-Null + + # относительные имена файлов из сценария кладём внутрь папки прогона + $argv = @() + for ($i = 0; $i -lt $r.args.Count; $i++) { + $v = $r.args[$i] + if ($i -gt 0 -and @('--gif','--xt','--case-csv') -contains $r.args[$i-1]) { $v = Join-Path $dir $v } + $argv += $v + } + $argv += @('--summary', (Join-Path $dir 'summary.json'), + '--csv', (Join-Path $dir 'series.csv'), + '--verbose','full','--report-every','5000') + + "$Bin $($argv -join ' ')" | Out-File (Join-Path $dir 'cmd.txt') -Encoding utf8 + "▶ $($r.id) ($($r.group), $($r.backend)) $($r.title)" + $t0 = Get-Date + & $Bin @argv 2>&1 | Out-File (Join-Path $dir 'log.txt') -Encoding utf8 + $st = if ($LASTEXITCODE -eq 0) { 'ok' } else { 'fail' } + $dt = [int]((Get-Date) - $t0).TotalSeconds + if (Select-String -Path (Join-Path $dir 'log.txt') -Pattern 'РАЗВАЛИЛСЯ' -Quiet) { $st = 'blewup' } + $rep = Get-Content (Join-Path $dir 'log.txt') -Encoding utf8 + $k = ($rep | Select-String 'ИТОГОВЫЙ ОТЧЁТ' | Select-Object -First 1).LineNumber + if ($k) { $rep[($k-1)..($rep.Count-1)] | Out-File (Join-Path $dir 'report.txt') -Encoding utf8 } + + '{0},{1},{2},{3},{4},{5},{6},"{7}"' -f $r.id,$r.group,$r.backend,$st,$dt,$r.steps,$r.cost,$r.title | + Out-File $summary -Append -Encoding utf8 + "$(Get-Date -Format o) $($r.id) $st ${dt}s" | Out-File $log -Append -Encoding utf8 + " → $st за $dt с" +} + +"" +"готово. сводная таблица: $summary" diff --git a/docs/theory/2d_solver/bench/run_campaign.sh b/docs/theory/2d_solver/bench/run_campaign.sh new file mode 100644 index 0000000..2157e6c --- /dev/null +++ b/docs/theory/2d_solver/bench/run_campaign.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash +# Драйвер валидационной кампании. Читает scenarios.json, гоняет прогоны по одному, кладёт +# логи, ряды, сводки и гифки каждого в собственную папку и ведёт общую таблицу. +# +# ./run_campaign.sh --dry-run смета: что и сколько будет считаться +# ./run_campaign.sh --calibrate замерить фактические MLUPS этой машины +# ./run_campaign.sh --smoke три коротких прогона: проверить обвязку +# ./run_campaign.sh --resume считать, пропуская уже готовое +# ./run_campaign.sh --group A,B только выбранные группы +# ./run_campaign.sh --only cyl_re150 по подстроке идентификатора +# ./run_campaign.sh --budget-hours 24 остановиться, когда время выйдет +# +# Прогон, который упал или развалился, помечается в сводке и НЕ останавливает кампанию: +# группы E и G специально ищут предел устойчивости. + +set -u +cd "$(dirname "$0")" + +BIN="${KBC2D_BIN:-../target/release/kbc2d}" +SCEN="${KBC2D_SCENARIOS:-scenarios.json}" +OUT="${KBC2D_OUT:-out}" +# ВНИМАНИЕ: имя GROUPS занято самим bash (список групп пользователя), присваивание в неё +# молча игнорируется, а "$GROUPS" под root разворачивается в 0 — и выборка съедает всю +# кампанию, не сказав ни слова. Отсюда GRP_SEL. +DRY=0; RESUME=0; CALIB=0; SMOKE=0; GRP_SEL=""; ONLY=""; BUDGET="" +GPU_MLUPS="${KBC2D_GPU_MLUPS:-1200}" +CPU_MLUPS="${KBC2D_CPU_MLUPS:-22}" + +while [ $# -gt 0 ]; do + case "$1" in + --dry-run) DRY=1 ;; + --resume) RESUME=1 ;; + --calibrate) CALIB=1 ;; + --smoke) SMOKE=1 ;; + --group) GRP_SEL="$2"; shift ;; + --only) ONLY="$2"; shift ;; + --budget-hours) BUDGET="$2"; shift ;; + -h|--help) sed -n '2,20p' "$0"; exit 0 ;; + *) echo "неизвестный ключ: $1" >&2; exit 2 ;; + esac + shift +done + +command -v python3 >/dev/null 2>&1 && PY=python3 || PY=python +[ -x "$BIN" ] || { echo "не найден бинарь решателя: $BIN (собери cargo build --release)" >&2; exit 1; } +[ -f "$SCEN" ] || { echo "не найден список сценариев: $SCEN" >&2; exit 1; } + +# ── калибровка: три коротких прогона на месте, чтобы оценки в часах были не гаданием ── +if [ "$CALIB" = 1 ]; then + echo "Калибровка на этой машине (три коротких прогона)…" + for spec in "240 120 gpu" "960 480 gpu" "480 240 cpu"; do + set -- $spec + m=$("$BIN" --nx "$1" --ny "$2" --size 16 --refine 1 --steps 3000 --report-every 3000 \ + --verbose full --backend "$3" 2>/dev/null | grep -o '[0-9.]* MLUPS' | tail -1) + echo " $1x$2 на $3: ${m:-не измерено}" + done + echo + echo "Подставь замеренное в переменные окружения и запусти смету:" + echo " KBC2D_GPU_MLUPS=<число> KBC2D_CPU_MLUPS=<число> ./run_campaign.sh --dry-run" + exit 0 +fi + +mkdir -p "$OUT" +SUMMARY="$OUT/summary.csv" +LOG="$OUT/campaign.log" +[ -f "$SUMMARY" ] || echo "id,group,backend,status,seconds,steps,cost,title" > "$SUMMARY" + +# Выборку и порядок считает python: разбирать JSON башем — верный способ ошибиться. +PLAN=$("$PY" - "$SCEN" "$GRP_SEL" "$ONLY" "$SMOKE" <<'PYEOF' +import json, sys +scen, groups, only, smoke = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] == "1" +runs = json.load(open(scen, encoding="utf-8"))["runs"] +if groups: + keep = {g.strip().upper() for g in groups.split(",")} + runs = [r for r in runs if r["group"].upper() in keep] +if only: + runs = [r for r in runs if only in r["id"]] +if smoke: + # три самых дешёвых прогона: обвязку проверяем, а не физику + runs = sorted(runs, key=lambda r: r["cost"])[:3] +for r in runs: + print("\t".join([r["id"], r["group"], r["backend"], str(r["cost"]), + str(r["steps"]), r["title"], "\x1f".join(r["args"])])) +PYEOF +) + +[ -n "$PLAN" ] || { echo "под выборку не попал ни один прогон"; exit 0; } + +total_cost=0; n=0 +while IFS=$'\t' read -r id grp bk cost steps title args; do + n=$((n + 1)); total_cost=$((total_cost + cost)) +done <<< "$PLAN" + +est_h=$("$PY" -c " +import sys +print('%.1f' % (float(sys.argv[1]) / (float(sys.argv[2]) * 1e6) / 3600))" "$total_cost" "$GPU_MLUPS") + +echo "прогонов: $n обновлений узлов: $total_cost ≈ $est_h ч при $GPU_MLUPS MLUPS" +echo "результаты: $OUT//" +echo + +if [ "$DRY" = 1 ]; then + printf '%-28s %-3s %-4s %12s %10s %s\n' id гр бэк шагов часов название + while IFS=$'\t' read -r id grp bk cost steps title args; do + m=$GPU_MLUPS; [ "$bk" = cpu ] && m=$CPU_MLUPS + h=$("$PY" -c "import sys;print('%.2f'%(float(sys.argv[1])/(float(sys.argv[2])*1e6)/3600))" "$cost" "$m") + printf '%-28s %-3s %-4s %12s %10s %s\n' "$id" "$grp" "$bk" "$steps" "$h" "$title" + done <<< "$PLAN" + exit 0 +fi + +started=$(date +%s) +while IFS=$'\t' read -r id grp bk cost steps title args; do + dir="$OUT/$id" + if [ "$RESUME" = 1 ] && [ -f "$dir/summary.json" ]; then + echo "· $id — уже посчитан, пропускаю" + continue + fi + if [ -n "$BUDGET" ]; then + spent=$(( ($(date +%s) - started) )) + limit=$("$PY" -c "import sys;print(int(float(sys.argv[1])*3600))" "$BUDGET") + [ "$spent" -ge "$limit" ] && { echo "бюджет $BUDGET ч исчерпан, останавливаюсь"; break; } + fi + + mkdir -p "$dir" + # аргументы разделены символом 0x1f: в них есть пробелы (список тел) и запятые + IFS=$'\x1f' read -r -a ARGV <<< "$args" + ARGV+=(--summary "$dir/summary.json" --csv "$dir/series.csv" --verbose full --report-every 5000) + # относительные имена файлов из сценария кладём внутрь папки прогона + for i in "${!ARGV[@]}"; do + case "${ARGV[$((i-1))]:-}" in + --gif|--xt|--case-csv) ARGV[$i]="$dir/${ARGV[$i]}" ;; + esac + done + + printf '%s\n' "$BIN ${ARGV[*]}" > "$dir/cmd.txt" + echo "▶ $id ($grp, $bk) $title" + t0=$(date +%s) + if "$BIN" "${ARGV[@]}" > "$dir/log.txt" 2>&1; then st=ok; else st=fail; fi + t1=$(date +%s); dt=$((t1 - t0)) + grep -q "РАЗВАЛИЛСЯ" "$dir/log.txt" 2>/dev/null && st=blewup + sed -n '/ИТОГОВЫЙ ОТЧЁТ/,$p' "$dir/log.txt" > "$dir/report.txt" 2>/dev/null + + printf '%s,%s,%s,%s,%s,%s,%s,"%s"\n' "$id" "$grp" "$bk" "$st" "$dt" "$steps" "$cost" "$title" >> "$SUMMARY" + echo "$(date -Is) $id $st ${dt}s" >> "$LOG" + echo " → $st за ${dt} с" +done <<< "$PLAN" + +echo +echo "готово. сводная таблица: $SUMMARY" diff --git a/docs/theory/2d_solver/bench/scenarios.json b/docs/theory/2d_solver/bench/scenarios.json new file mode 100644 index 0000000..9b6099d --- /dev/null +++ b/docs/theory/2d_solver/bench/scenarios.json @@ -0,0 +1,4853 @@ +{ + "meta": { + "note": "Стоимость в обновлениях узлов — единственная переносимая между машинами мера. Часы драйвер получает, поделив её на фактические MLUPS, которые замеряет на месте (--calibrate).", + "gif_policy": "вся длительность прогона, реальная скорость, 10 кадр/с; кадр прореживается до ширины не больше 1280 пикселей", + "scale": 2.0, + "assumed_gpu_mlups": 1200, + "assumed_cpu_mlups": 22 + }, + "runs": [ + { + "id": "A01_tg_diff_n64", + "group": "A", + "title": "Тейлор–Грин, диффузионное измельчение, N=64 (CPU/f64)", + "expect": "порядок сходимости 2 по ряду N=64..512; ошибка на N=512 ниже 2e-4", + "backend": "cpu", + "args": [ + "--case", + "taylor-green", + "--nx", + "64", + "--ny", + "64", + "--refine", + "1", + "--u-lat", + "0.030000", + "--re", + "100", + "--case-every", + "11", + "--steps", + "220", + "--backend", + "cpu", + "--case-csv", + "A01_tg_diff_n64_case.csv" + ], + "nodes_per_step": 4096, + "steps": 220, + "cost": 901120, + "outputs": [ + "case_csv" + ] + }, + { + "id": "A02_tg_diff_n128", + "group": "A", + "title": "Тейлор–Грин, диффузионное измельчение, N=128 (CPU/f64)", + "expect": "порядок сходимости 2 по ряду N=64..512; ошибка на N=512 ниже 2e-4", + "backend": "cpu", + "args": [ + "--case", + "taylor-green", + "--nx", + "128", + "--ny", + "128", + "--refine", + "1", + "--u-lat", + "0.015000", + "--re", + "100", + "--case-every", + "44", + "--steps", + "881", + "--backend", + "cpu", + "--case-csv", + "A02_tg_diff_n128_case.csv" + ], + "nodes_per_step": 16384, + "steps": 881, + "cost": 14434304, + "outputs": [ + "case_csv" + ] + }, + { + "id": "A03_tg_diff_n256", + "group": "A", + "title": "Тейлор–Грин, диффузионное измельчение, N=256 (CPU/f64)", + "expect": "порядок сходимости 2 по ряду N=64..512; ошибка на N=512 ниже 2e-4", + "backend": "cpu", + "args": [ + "--case", + "taylor-green", + "--nx", + "256", + "--ny", + "256", + "--refine", + "1", + "--u-lat", + "0.007500", + "--re", + "100", + "--case-every", + "176", + "--steps", + "3525", + "--backend", + "cpu", + "--case-csv", + "A03_tg_diff_n256_case.csv" + ], + "nodes_per_step": 65536, + "steps": 3525, + "cost": 231014400, + "outputs": [ + "case_csv" + ] + }, + { + "id": "A04_tg_diff_n512", + "group": "A", + "title": "Тейлор–Грин, диффузионное измельчение, N=512 (CPU/f64)", + "expect": "порядок сходимости 2 по ряду N=64..512; ошибка на N=512 ниже 2e-4", + "backend": "cpu", + "args": [ + "--case", + "taylor-green", + "--nx", + "512", + "--ny", + "512", + "--refine", + "1", + "--u-lat", + "0.003750", + "--re", + "100", + "--case-every", + "705", + "--steps", + "14101", + "--backend", + "cpu", + "--case-csv", + "A04_tg_diff_n512_case.csv" + ], + "nodes_per_step": 262144, + "steps": 14101, + "cost": 3696492544, + "outputs": [ + "case_csv" + ] + }, + { + "id": "A05_tg_fixed_n128", + "group": "A", + "title": "Тейлор–Грин при фиксированном u₀, N=128 — демонстрация полки O(Ma²)", + "expect": "ошибка перестаёт падать с ростом N: это свойство слабо-сжимаемого метода", + "backend": "cpu", + "args": [ + "--case", + "taylor-green", + "--nx", + "128", + "--ny", + "128", + "--refine", + "1", + "--u-lat", + "0.03", + "--re", + "200.0", + "--case-every", + "44", + "--steps", + "881", + "--backend", + "cpu", + "--case-csv", + "A05_tg_fixed_n128_case.csv" + ], + "nodes_per_step": 16384, + "steps": 881, + "cost": 14434304, + "outputs": [ + "case_csv" + ] + }, + { + "id": "A06_tg_fixed_n256", + "group": "A", + "title": "Тейлор–Грин при фиксированном u₀, N=256 — демонстрация полки O(Ma²)", + "expect": "ошибка перестаёт падать с ростом N: это свойство слабо-сжимаемого метода", + "backend": "cpu", + "args": [ + "--case", + "taylor-green", + "--nx", + "256", + "--ny", + "256", + "--refine", + "1", + "--u-lat", + "0.03", + "--re", + "400.0", + "--case-every", + "176", + "--steps", + "3525", + "--backend", + "cpu", + "--case-csv", + "A06_tg_fixed_n256_case.csv" + ], + "nodes_per_step": 65536, + "steps": 3525, + "cost": 231014400, + "outputs": [ + "case_csv" + ] + }, + { + "id": "A07_shear_n512_kbc_n1", + "group": "A", + "title": "Сдвиговый слой Re=3·10⁴, N=512, kbc_n1", + "expect": "KBC доживает до конца, LBGK обязан развалиться (разд. VII статьи)", + "backend": "gpu", + "args": [ + "--case", + "shear-layer", + "--nx", + "512", + "--ny", + "512", + "--refine", + "1", + "--u-lat", + "0.04", + "--re", + "30000", + "--collision", + "kbc", + "--kbc-model", + "n1", + "--case-every", + "512", + "--steps", + "102400", + "--backend", + "gpu", + "--case-csv", + "A07_shear_n512_kbc_n1_case.csv", + "--gif", + "shear_kbc_n1.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 262144, + "steps": 102400, + "cost": 26843545600, + "outputs": [ + "case_csv" + ] + }, + { + "id": "A08_shear_n512_kbc_n2", + "group": "A", + "title": "Сдвиговый слой Re=3·10⁴, N=512, kbc_n2", + "expect": "KBC доживает до конца, LBGK обязан развалиться (разд. VII статьи)", + "backend": "gpu", + "args": [ + "--case", + "shear-layer", + "--nx", + "512", + "--ny", + "512", + "--refine", + "1", + "--u-lat", + "0.04", + "--re", + "30000", + "--collision", + "kbc", + "--kbc-model", + "n2", + "--case-every", + "512", + "--steps", + "102400", + "--backend", + "gpu", + "--case-csv", + "A08_shear_n512_kbc_n2_case.csv", + "--gif", + "shear_kbc_n2.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 262144, + "steps": 102400, + "cost": 26843545600, + "outputs": [ + "case_csv" + ] + }, + { + "id": "A09_shear_n512_lbgk", + "group": "A", + "title": "Сдвиговый слой Re=3·10⁴, N=512, lbgk", + "expect": "KBC доживает до конца, LBGK обязан развалиться (разд. VII статьи)", + "backend": "gpu", + "args": [ + "--case", + "shear-layer", + "--nx", + "512", + "--ny", + "512", + "--refine", + "1", + "--u-lat", + "0.04", + "--re", + "30000", + "--collision", + "bgk", + "--kbc-model", + "n1", + "--case-every", + "512", + "--steps", + "102400", + "--backend", + "gpu", + "--case-csv", + "A09_shear_n512_lbgk_case.csv", + "--gif", + "shear_lbgk.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 262144, + "steps": 102400, + "cost": 26843545600, + "outputs": [ + "case_csv" + ] + }, + { + "id": "A10_turb_n2048_kbc", + "group": "A", + "title": "Затухающая турбулентность N=2048, kbc", + "expect": "энстрофия и палинстрофия падают монотонно; KBC держится там, где LBGK нет", + "backend": "gpu", + "args": [ + "--case", + "decaying-turbulence", + "--nx", + "2048", + "--ny", + "2048", + "--refine", + "1", + "--u-lat", + "0.02", + "--re", + "501961", + "--collision", + "kbc", + "--case-every", + "1333", + "--steps", + "400000", + "--backend", + "gpu", + "--case-csv", + "A10_turb_n2048_kbc_case.csv", + "--gif", + "turb_n2048_kbc.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "2", + "--gif-scale", + "1" + ], + "nodes_per_step": 4194304, + "steps": 400000, + "cost": 1677721600000, + "outputs": [ + "case_csv" + ] + }, + { + "id": "A11_turb_n2048_bgk", + "group": "A", + "title": "Затухающая турбулентность N=2048, bgk", + "expect": "энстрофия и палинстрофия падают монотонно; KBC держится там, где LBGK нет", + "backend": "gpu", + "args": [ + "--case", + "decaying-turbulence", + "--nx", + "2048", + "--ny", + "2048", + "--refine", + "1", + "--u-lat", + "0.02", + "--re", + "501961", + "--collision", + "bgk", + "--case-every", + "1333", + "--steps", + "400000", + "--backend", + "gpu", + "--case-csv", + "A11_turb_n2048_bgk_case.csv", + "--gif", + "turb_n2048_bgk.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "2", + "--gif-scale", + "1" + ], + "nodes_per_step": 4194304, + "steps": 400000, + "cost": 1677721600000, + "outputs": [ + "case_csv" + ] + }, + { + "id": "A12_turb_n4096_kbc", + "group": "A", + "title": "Затухающая турбулентность N=4096, kbc", + "expect": "энстрофия и палинстрофия падают монотонно; KBC держится там, где LBGK нет", + "backend": "gpu", + "args": [ + "--case", + "decaying-turbulence", + "--nx", + "4096", + "--ny", + "4096", + "--refine", + "1", + "--u-lat", + "0.02", + "--re", + "1003922", + "--collision", + "kbc", + "--case-every", + "666", + "--steps", + "200000", + "--backend", + "gpu", + "--case-csv", + "A12_turb_n4096_kbc_case.csv", + "--gif", + "turb_n4096_kbc.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "4", + "--gif-scale", + "1" + ], + "nodes_per_step": 16777216, + "steps": 200000, + "cost": 3355443200000, + "outputs": [ + "case_csv" + ] + }, + { + "id": "B01_cyl_re20", + "group": "B", + "title": "Цилиндр Re=20, стационар, D=48", + "expect": "Cd выходит на постоянную; сравнение с литературой по стационарному обтеканию", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "20", + "--refine", + "1", + "--pert-amp", + "0", + "--steps", + "115200", + "--backend", + "gpu", + "--gif", + "cyl_re20.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 552960, + "steps": 115200, + "cost": 63700992000, + "outputs": [] + }, + { + "id": "B02_cyl_re40", + "group": "B", + "title": "Цилиндр Re=40, стационар, D=48", + "expect": "Cd выходит на постоянную; сравнение с литературой по стационарному обтеканию", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "40", + "--refine", + "1", + "--pert-amp", + "0", + "--steps", + "115200", + "--backend", + "gpu", + "--gif", + "cyl_re40.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 552960, + "steps": 115200, + "cost": 63700992000, + "outputs": [] + }, + { + "id": "B03_cyl_re100", + "group": "B", + "title": "Цилиндр Re=100, дорожка Кармана, D=64, 1500 конв. времён", + "expect": "St≈0.183 и ⟨Cd⟩≈1.33 при Re=150 после поправки на блокировку", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "64", + "--nx", + "1280", + "--ny", + "768", + "--body-x", + "256", + "--re", + "100", + "--refine", + "1", + "--steps", + "3840000", + "--backend", + "gpu", + "--series-every", + "19", + "--gif", + "cyl_re100.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "2", + "--gif-scale", + "1" + ], + "nodes_per_step": 983040, + "steps": 3840000, + "cost": 3774873600000, + "outputs": [] + }, + { + "id": "B04_cyl_re150", + "group": "B", + "title": "Цилиндр Re=150, дорожка Кармана, D=64, 1500 конв. времён", + "expect": "St≈0.183 и ⟨Cd⟩≈1.33 при Re=150 после поправки на блокировку", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "64", + "--nx", + "1280", + "--ny", + "768", + "--body-x", + "256", + "--re", + "150", + "--refine", + "1", + "--steps", + "3840000", + "--backend", + "gpu", + "--series-every", + "19", + "--gif", + "cyl_re150.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "2", + "--gif-scale", + "1" + ], + "nodes_per_step": 983040, + "steps": 3840000, + "cost": 3774873600000, + "outputs": [] + }, + { + "id": "B05_cyl_re200", + "group": "B", + "title": "Цилиндр Re=200, дорожка Кармана, D=64, 1500 конв. времён", + "expect": "St≈0.183 и ⟨Cd⟩≈1.33 при Re=150 после поправки на блокировку", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "64", + "--nx", + "1280", + "--ny", + "768", + "--body-x", + "256", + "--re", + "200", + "--refine", + "1", + "--steps", + "3840000", + "--backend", + "gpu", + "--series-every", + "19", + "--gif", + "cyl_re200.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "2", + "--gif-scale", + "1" + ], + "nodes_per_step": 983040, + "steps": 3840000, + "cost": 3774873600000, + "outputs": [] + }, + { + "id": "B06_cyl_re300", + "group": "B", + "title": "Цилиндр Re=300, дорожка Кармана, D=64, 1500 конв. времён", + "expect": "St≈0.183 и ⟨Cd⟩≈1.33 при Re=150 после поправки на блокировку", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "64", + "--nx", + "1280", + "--ny", + "768", + "--body-x", + "256", + "--re", + "300", + "--refine", + "1", + "--steps", + "3840000", + "--backend", + "gpu", + "--series-every", + "19", + "--gif", + "cyl_re300.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "2", + "--gif-scale", + "1" + ], + "nodes_per_step": 983040, + "steps": 3840000, + "cost": 3774873600000, + "outputs": [] + }, + { + "id": "B07_cyl_d16", + "group": "B", + "title": "Цилиндр Re=150, разрешение D=16", + "expect": "Cd сходится по D; экстраполяция даёт сеточно-независимое значение", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "16", + "--nx", + "320", + "--ny", + "192", + "--body-x", + "64", + "--re", + "150", + "--refine", + "1", + "--steps", + "512000", + "--backend", + "gpu", + "--series-every", + "2", + "--gif", + "cyl_d16.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 61440, + "steps": 512000, + "cost": 31457280000, + "outputs": [] + }, + { + "id": "B08_cyl_d32", + "group": "B", + "title": "Цилиндр Re=150, разрешение D=32", + "expect": "Cd сходится по D; экстраполяция даёт сеточно-независимое значение", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "150", + "--refine", + "1", + "--steps", + "1024000", + "--backend", + "gpu", + "--series-every", + "5", + "--gif", + "cyl_d32.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 245760, + "steps": 1024000, + "cost": 251658240000, + "outputs": [] + }, + { + "id": "B09_cyl_d64", + "group": "B", + "title": "Цилиндр Re=150, разрешение D=64", + "expect": "Cd сходится по D; экстраполяция даёт сеточно-независимое значение", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "64", + "--nx", + "1280", + "--ny", + "768", + "--body-x", + "256", + "--re", + "150", + "--refine", + "1", + "--steps", + "2048000", + "--backend", + "gpu", + "--series-every", + "10", + "--gif", + "cyl_d64.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "2", + "--gif-scale", + "1" + ], + "nodes_per_step": 983040, + "steps": 2048000, + "cost": 2013265920000, + "outputs": [] + }, + { + "id": "B10_cyl_d128", + "group": "B", + "title": "Цилиндр Re=150, разрешение D=128", + "expect": "Cd сходится по D; экстраполяция даёт сеточно-независимое значение", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "128", + "--nx", + "2560", + "--ny", + "1536", + "--body-x", + "512", + "--re", + "150", + "--refine", + "1", + "--steps", + "4096000", + "--backend", + "gpu", + "--series-every", + "20", + "--gif", + "cyl_d128.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "5", + "--gif-scale", + "1" + ], + "nodes_per_step": 3932160, + "steps": 4096000, + "cost": 16106127360000, + "outputs": [] + }, + { + "id": "B11_cyl_block6", + "group": "B", + "title": "Цилиндр Re=150, блокировка Ny/D=6", + "expect": "экстраполяция Cd и St к нулевой блокировке; ожидание — сходимость к 1.33 и 0.183", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "288", + "--body-x", + "192", + "--re", + "150", + "--refine", + "1", + "--steps", + "1920000", + "--backend", + "gpu", + "--series-every", + "9" + ], + "nodes_per_step": 276480, + "steps": 1920000, + "cost": 530841600000, + "outputs": [] + }, + { + "id": "B12_cyl_block8", + "group": "B", + "title": "Цилиндр Re=150, блокировка Ny/D=8", + "expect": "экстраполяция Cd и St к нулевой блокировке; ожидание — сходимость к 1.33 и 0.183", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "384", + "--body-x", + "192", + "--re", + "150", + "--refine", + "1", + "--steps", + "1920000", + "--backend", + "gpu", + "--series-every", + "9" + ], + "nodes_per_step": 368640, + "steps": 1920000, + "cost": 707788800000, + "outputs": [] + }, + { + "id": "B13_cyl_block12", + "group": "B", + "title": "Цилиндр Re=150, блокировка Ny/D=12", + "expect": "экстраполяция Cd и St к нулевой блокировке; ожидание — сходимость к 1.33 и 0.183", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "150", + "--refine", + "1", + "--steps", + "1920000", + "--backend", + "gpu", + "--series-every", + "9" + ], + "nodes_per_step": 552960, + "steps": 1920000, + "cost": 1061683200000, + "outputs": [] + }, + { + "id": "B14_cyl_block16", + "group": "B", + "title": "Цилиндр Re=150, блокировка Ny/D=16", + "expect": "экстраполяция Cd и St к нулевой блокировке; ожидание — сходимость к 1.33 и 0.183", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "768", + "--body-x", + "192", + "--re", + "150", + "--refine", + "1", + "--steps", + "1920000", + "--backend", + "gpu", + "--series-every", + "9" + ], + "nodes_per_step": 737280, + "steps": 1920000, + "cost": 1415577600000, + "outputs": [] + }, + { + "id": "B15_cyl_block24", + "group": "B", + "title": "Цилиндр Re=150, блокировка Ny/D=24", + "expect": "экстраполяция Cd и St к нулевой блокировке; ожидание — сходимость к 1.33 и 0.183", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "1152", + "--body-x", + "192", + "--re", + "150", + "--refine", + "1", + "--steps", + "1920000", + "--backend", + "gpu", + "--series-every", + "9" + ], + "nodes_per_step": 1105920, + "steps": 1920000, + "cost": 2123366400000, + "outputs": [] + }, + { + "id": "B16_cyl_block32", + "group": "B", + "title": "Цилиндр Re=150, блокировка Ny/D=32", + "expect": "экстраполяция Cd и St к нулевой блокировке; ожидание — сходимость к 1.33 и 0.183", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "1536", + "--body-x", + "192", + "--re", + "150", + "--refine", + "1", + "--steps", + "1920000", + "--backend", + "gpu", + "--series-every", + "9" + ], + "nodes_per_step": 1474560, + "steps": 1920000, + "cost": 2831155200000, + "outputs": [] + }, + { + "id": "C01_wall_hrr_re150", + "group": "C", + "title": "Модель стенки hrr, Re=150", + "expect": "все субсеточные модели обязаны сойтись к одному пределу; staircase — база", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "150", + "--refine", + "1", + "--wall", + "hrr", + "--steps", + "512000", + "--backend", + "gpu", + "--series-every", + "2", + "--gif", + "wall_hrr_re150.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 245760, + "steps": 512000, + "cost": 125829120000, + "outputs": [] + }, + { + "id": "C02_wall_hrr_re2000", + "group": "C", + "title": "Модель стенки hrr, Re=2000", + "expect": "все субсеточные модели обязаны сойтись к одному пределу; staircase — база", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "2000", + "--refine", + "1", + "--wall", + "hrr", + "--steps", + "512000", + "--backend", + "gpu", + "--series-every", + "2" + ], + "nodes_per_step": 245760, + "steps": 512000, + "cost": 125829120000, + "outputs": [] + }, + { + "id": "C03_wall_grad_re150", + "group": "C", + "title": "Модель стенки grad, Re=150", + "expect": "все субсеточные модели обязаны сойтись к одному пределу; staircase — база", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "150", + "--refine", + "1", + "--wall", + "grad", + "--steps", + "512000", + "--backend", + "gpu", + "--series-every", + "2", + "--gif", + "wall_grad_re150.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 245760, + "steps": 512000, + "cost": 125829120000, + "outputs": [] + }, + { + "id": "C04_wall_grad_re2000", + "group": "C", + "title": "Модель стенки grad, Re=2000", + "expect": "все субсеточные модели обязаны сойтись к одному пределу; staircase — база", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "2000", + "--refine", + "1", + "--wall", + "grad", + "--steps", + "512000", + "--backend", + "gpu", + "--series-every", + "2" + ], + "nodes_per_step": 245760, + "steps": 512000, + "cost": 125829120000, + "outputs": [] + }, + { + "id": "C05_wall_bouzidi_re150", + "group": "C", + "title": "Модель стенки bouzidi, Re=150", + "expect": "все субсеточные модели обязаны сойтись к одному пределу; staircase — база", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "150", + "--refine", + "1", + "--wall", + "bouzidi", + "--steps", + "512000", + "--backend", + "gpu", + "--series-every", + "2", + "--gif", + "wall_bouzidi_re150.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 245760, + "steps": 512000, + "cost": 125829120000, + "outputs": [] + }, + { + "id": "C06_wall_bouzidi_re2000", + "group": "C", + "title": "Модель стенки bouzidi, Re=2000", + "expect": "все субсеточные модели обязаны сойтись к одному пределу; staircase — база", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "2000", + "--refine", + "1", + "--wall", + "bouzidi", + "--steps", + "512000", + "--backend", + "gpu", + "--series-every", + "2" + ], + "nodes_per_step": 245760, + "steps": 512000, + "cost": 125829120000, + "outputs": [] + }, + { + "id": "C07_wall_staircase_re150", + "group": "C", + "title": "Модель стенки staircase, Re=150", + "expect": "все субсеточные модели обязаны сойтись к одному пределу; staircase — база", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "150", + "--refine", + "1", + "--wall", + "staircase", + "--steps", + "512000", + "--backend", + "gpu", + "--series-every", + "2", + "--gif", + "wall_staircase_re150.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 245760, + "steps": 512000, + "cost": 125829120000, + "outputs": [] + }, + { + "id": "C08_wall_staircase_re2000", + "group": "C", + "title": "Модель стенки staircase, Re=2000", + "expect": "все субсеточные модели обязаны сойтись к одному пределу; staircase — база", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "2000", + "--refine", + "1", + "--wall", + "staircase", + "--steps", + "512000", + "--backend", + "gpu", + "--series-every", + "2" + ], + "nodes_per_step": 245760, + "steps": 512000, + "cost": 125829120000, + "outputs": [] + }, + { + "id": "C09_sub_hrr_00", + "group": "C", + "title": "Субклеточный сдвиг тела 0.0 клетки, стенка hrr", + "expect": "разброс Cd по сдвигам: чем субсеточнее модель, тем он меньше", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "16", + "--nx", + "320", + "--ny", + "192", + "--body-x", + "64", + "--re", + "20", + "--refine", + "1", + "--body-y", + "96.0", + "--wall", + "hrr", + "--pert-amp", + "0", + "--steps", + "38400", + "--backend", + "gpu" + ], + "nodes_per_step": 61440, + "steps": 38400, + "cost": 2359296000, + "outputs": [] + }, + { + "id": "C10_sub_hrr_25", + "group": "C", + "title": "Субклеточный сдвиг тела 0.25 клетки, стенка hrr", + "expect": "разброс Cd по сдвигам: чем субсеточнее модель, тем он меньше", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "16", + "--nx", + "320", + "--ny", + "192", + "--body-x", + "64", + "--re", + "20", + "--refine", + "1", + "--body-y", + "96.25", + "--wall", + "hrr", + "--pert-amp", + "0", + "--steps", + "38400", + "--backend", + "gpu" + ], + "nodes_per_step": 61440, + "steps": 38400, + "cost": 2359296000, + "outputs": [] + }, + { + "id": "C11_sub_hrr_50", + "group": "C", + "title": "Субклеточный сдвиг тела 0.5 клетки, стенка hrr", + "expect": "разброс Cd по сдвигам: чем субсеточнее модель, тем он меньше", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "16", + "--nx", + "320", + "--ny", + "192", + "--body-x", + "64", + "--re", + "20", + "--refine", + "1", + "--body-y", + "96.5", + "--wall", + "hrr", + "--pert-amp", + "0", + "--steps", + "38400", + "--backend", + "gpu" + ], + "nodes_per_step": 61440, + "steps": 38400, + "cost": 2359296000, + "outputs": [] + }, + { + "id": "C12_sub_grad_00", + "group": "C", + "title": "Субклеточный сдвиг тела 0.0 клетки, стенка grad", + "expect": "разброс Cd по сдвигам: чем субсеточнее модель, тем он меньше", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "16", + "--nx", + "320", + "--ny", + "192", + "--body-x", + "64", + "--re", + "20", + "--refine", + "1", + "--body-y", + "96.0", + "--wall", + "grad", + "--pert-amp", + "0", + "--steps", + "38400", + "--backend", + "gpu" + ], + "nodes_per_step": 61440, + "steps": 38400, + "cost": 2359296000, + "outputs": [] + }, + { + "id": "C13_sub_grad_25", + "group": "C", + "title": "Субклеточный сдвиг тела 0.25 клетки, стенка grad", + "expect": "разброс Cd по сдвигам: чем субсеточнее модель, тем он меньше", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "16", + "--nx", + "320", + "--ny", + "192", + "--body-x", + "64", + "--re", + "20", + "--refine", + "1", + "--body-y", + "96.25", + "--wall", + "grad", + "--pert-amp", + "0", + "--steps", + "38400", + "--backend", + "gpu" + ], + "nodes_per_step": 61440, + "steps": 38400, + "cost": 2359296000, + "outputs": [] + }, + { + "id": "C14_sub_grad_50", + "group": "C", + "title": "Субклеточный сдвиг тела 0.5 клетки, стенка grad", + "expect": "разброс Cd по сдвигам: чем субсеточнее модель, тем он меньше", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "16", + "--nx", + "320", + "--ny", + "192", + "--body-x", + "64", + "--re", + "20", + "--refine", + "1", + "--body-y", + "96.5", + "--wall", + "grad", + "--pert-amp", + "0", + "--steps", + "38400", + "--backend", + "gpu" + ], + "nodes_per_step": 61440, + "steps": 38400, + "cost": 2359296000, + "outputs": [] + }, + { + "id": "C15_sub_bouzidi_00", + "group": "C", + "title": "Субклеточный сдвиг тела 0.0 клетки, стенка bouzidi", + "expect": "разброс Cd по сдвигам: чем субсеточнее модель, тем он меньше", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "16", + "--nx", + "320", + "--ny", + "192", + "--body-x", + "64", + "--re", + "20", + "--refine", + "1", + "--body-y", + "96.0", + "--wall", + "bouzidi", + "--pert-amp", + "0", + "--steps", + "38400", + "--backend", + "gpu" + ], + "nodes_per_step": 61440, + "steps": 38400, + "cost": 2359296000, + "outputs": [] + }, + { + "id": "C16_sub_bouzidi_25", + "group": "C", + "title": "Субклеточный сдвиг тела 0.25 клетки, стенка bouzidi", + "expect": "разброс Cd по сдвигам: чем субсеточнее модель, тем он меньше", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "16", + "--nx", + "320", + "--ny", + "192", + "--body-x", + "64", + "--re", + "20", + "--refine", + "1", + "--body-y", + "96.25", + "--wall", + "bouzidi", + "--pert-amp", + "0", + "--steps", + "38400", + "--backend", + "gpu" + ], + "nodes_per_step": 61440, + "steps": 38400, + "cost": 2359296000, + "outputs": [] + }, + { + "id": "C17_sub_bouzidi_50", + "group": "C", + "title": "Субклеточный сдвиг тела 0.5 клетки, стенка bouzidi", + "expect": "разброс Cd по сдвигам: чем субсеточнее модель, тем он меньше", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "16", + "--nx", + "320", + "--ny", + "192", + "--body-x", + "64", + "--re", + "20", + "--refine", + "1", + "--body-y", + "96.5", + "--wall", + "bouzidi", + "--pert-amp", + "0", + "--steps", + "38400", + "--backend", + "gpu" + ], + "nodes_per_step": 61440, + "steps": 38400, + "cost": 2359296000, + "outputs": [] + }, + { + "id": "C18_sub_staircase_00", + "group": "C", + "title": "Субклеточный сдвиг тела 0.0 клетки, стенка staircase", + "expect": "разброс Cd по сдвигам: чем субсеточнее модель, тем он меньше", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "16", + "--nx", + "320", + "--ny", + "192", + "--body-x", + "64", + "--re", + "20", + "--refine", + "1", + "--body-y", + "96.0", + "--wall", + "staircase", + "--pert-amp", + "0", + "--steps", + "38400", + "--backend", + "gpu" + ], + "nodes_per_step": 61440, + "steps": 38400, + "cost": 2359296000, + "outputs": [] + }, + { + "id": "C19_sub_staircase_25", + "group": "C", + "title": "Субклеточный сдвиг тела 0.25 клетки, стенка staircase", + "expect": "разброс Cd по сдвигам: чем субсеточнее модель, тем он меньше", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "16", + "--nx", + "320", + "--ny", + "192", + "--body-x", + "64", + "--re", + "20", + "--refine", + "1", + "--body-y", + "96.25", + "--wall", + "staircase", + "--pert-amp", + "0", + "--steps", + "38400", + "--backend", + "gpu" + ], + "nodes_per_step": 61440, + "steps": 38400, + "cost": 2359296000, + "outputs": [] + }, + { + "id": "C20_sub_staircase_50", + "group": "C", + "title": "Субклеточный сдвиг тела 0.5 клетки, стенка staircase", + "expect": "разброс Cd по сдвигам: чем субсеточнее модель, тем он меньше", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "16", + "--nx", + "320", + "--ny", + "192", + "--body-x", + "64", + "--re", + "20", + "--refine", + "1", + "--body-y", + "96.5", + "--wall", + "staircase", + "--pert-amp", + "0", + "--steps", + "38400", + "--backend", + "gpu" + ], + "nodes_per_step": 61440, + "steps": 38400, + "cost": 2359296000, + "outputs": [] + }, + { + "id": "D01_naca0012_a00", + "group": "D", + "title": "NACA 0012, α=0°, Re=1000, хорда 128", + "expect": "линейный участок Cl(α) и срыв; сравнение с низкорейнольдсовой литературой", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "128", + "--body-angle", + "0", + "--nx", + "2048", + "--ny", + "1024", + "--body-x", + "512", + "--re", + "1000", + "--refine", + "1", + "--steps", + "2048000", + "--backend", + "gpu", + "--series-every", + "10", + "--gif", + "naca0012_a00.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "3", + "--gif-scale", + "1" + ], + "nodes_per_step": 2097152, + "steps": 2048000, + "cost": 4294967296000, + "outputs": [] + }, + { + "id": "D02_naca0012_a04", + "group": "D", + "title": "NACA 0012, α=4°, Re=1000, хорда 128", + "expect": "линейный участок Cl(α) и срыв; сравнение с низкорейнольдсовой литературой", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "128", + "--body-angle", + "4", + "--nx", + "2048", + "--ny", + "1024", + "--body-x", + "512", + "--re", + "1000", + "--refine", + "1", + "--steps", + "2048000", + "--backend", + "gpu", + "--series-every", + "10", + "--gif", + "naca0012_a04.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "3", + "--gif-scale", + "1" + ], + "nodes_per_step": 2097152, + "steps": 2048000, + "cost": 4294967296000, + "outputs": [] + }, + { + "id": "D03_naca0012_a08", + "group": "D", + "title": "NACA 0012, α=8°, Re=1000, хорда 128", + "expect": "линейный участок Cl(α) и срыв; сравнение с низкорейнольдсовой литературой", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "128", + "--body-angle", + "8", + "--nx", + "2048", + "--ny", + "1024", + "--body-x", + "512", + "--re", + "1000", + "--refine", + "1", + "--steps", + "2048000", + "--backend", + "gpu", + "--series-every", + "10", + "--gif", + "naca0012_a08.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "3", + "--gif-scale", + "1" + ], + "nodes_per_step": 2097152, + "steps": 2048000, + "cost": 4294967296000, + "outputs": [] + }, + { + "id": "D04_naca0012_a12", + "group": "D", + "title": "NACA 0012, α=12°, Re=1000, хорда 128", + "expect": "линейный участок Cl(α) и срыв; сравнение с низкорейнольдсовой литературой", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "128", + "--body-angle", + "12", + "--nx", + "2048", + "--ny", + "1024", + "--body-x", + "512", + "--re", + "1000", + "--refine", + "1", + "--steps", + "2048000", + "--backend", + "gpu", + "--series-every", + "10", + "--gif", + "naca0012_a12.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "3", + "--gif-scale", + "1" + ], + "nodes_per_step": 2097152, + "steps": 2048000, + "cost": 4294967296000, + "outputs": [] + }, + { + "id": "D05_naca0012_a16", + "group": "D", + "title": "NACA 0012, α=16°, Re=1000, хорда 128", + "expect": "линейный участок Cl(α) и срыв; сравнение с низкорейнольдсовой литературой", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "128", + "--body-angle", + "16", + "--nx", + "2048", + "--ny", + "1024", + "--body-x", + "512", + "--re", + "1000", + "--refine", + "1", + "--steps", + "2048000", + "--backend", + "gpu", + "--series-every", + "10", + "--gif", + "naca0012_a16.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "3", + "--gif-scale", + "1" + ], + "nodes_per_step": 2097152, + "steps": 2048000, + "cost": 4294967296000, + "outputs": [] + }, + { + "id": "D06_naca0012_re500", + "group": "D", + "title": "NACA 0012, α=8°, Re=500", + "expect": "перестройка следа с ростом Re; проверка устойчивости на профиле", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "128", + "--body-angle", + "8", + "--nx", + "2048", + "--ny", + "1024", + "--body-x", + "512", + "--re", + "500", + "--refine", + "1", + "--steps", + "2048000", + "--backend", + "gpu", + "--series-every", + "10", + "--gif", + "naca0012_re500.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "3", + "--gif-scale", + "1" + ], + "nodes_per_step": 2097152, + "steps": 2048000, + "cost": 4294967296000, + "outputs": [] + }, + { + "id": "D07_naca0012_re2000", + "group": "D", + "title": "NACA 0012, α=8°, Re=2000", + "expect": "перестройка следа с ростом Re; проверка устойчивости на профиле", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "128", + "--body-angle", + "8", + "--nx", + "2048", + "--ny", + "1024", + "--body-x", + "512", + "--re", + "2000", + "--refine", + "1", + "--steps", + "2048000", + "--backend", + "gpu", + "--series-every", + "10", + "--gif", + "naca0012_re2000.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "3", + "--gif-scale", + "1" + ], + "nodes_per_step": 2097152, + "steps": 2048000, + "cost": 4294967296000, + "outputs": [] + }, + { + "id": "D08_naca0012_re10000", + "group": "D", + "title": "NACA 0012, α=8°, Re=10000", + "expect": "перестройка следа с ростом Re; проверка устойчивости на профиле", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "128", + "--body-angle", + "8", + "--nx", + "2048", + "--ny", + "1024", + "--body-x", + "512", + "--re", + "10000", + "--refine", + "1", + "--steps", + "2048000", + "--backend", + "gpu", + "--series-every", + "10", + "--gif", + "naca0012_re10000.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "3", + "--gif-scale", + "1" + ], + "nodes_per_step": 2097152, + "steps": 2048000, + "cost": 4294967296000, + "outputs": [] + }, + { + "id": "D09_naca4412_a00", + "group": "D", + "title": "NACA 4412 (с изгибом), α=0°, Re=1000", + "expect": "ненулевой Cl при α=0 — прямая проверка средней линии профиля", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "4412", + "--size", + "128", + "--body-angle", + "0", + "--nx", + "2048", + "--ny", + "1024", + "--body-x", + "512", + "--re", + "1000", + "--refine", + "1", + "--steps", + "2048000", + "--backend", + "gpu", + "--series-every", + "10", + "--gif", + "naca4412_a00.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "3", + "--gif-scale", + "1" + ], + "nodes_per_step": 2097152, + "steps": 2048000, + "cost": 4294967296000, + "outputs": [] + }, + { + "id": "D10_naca4412_a06", + "group": "D", + "title": "NACA 4412 (с изгибом), α=6°, Re=1000", + "expect": "ненулевой Cl при α=0 — прямая проверка средней линии профиля", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "4412", + "--size", + "128", + "--body-angle", + "6", + "--nx", + "2048", + "--ny", + "1024", + "--body-x", + "512", + "--re", + "1000", + "--refine", + "1", + "--steps", + "2048000", + "--backend", + "gpu", + "--series-every", + "10", + "--gif", + "naca4412_a06.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "3", + "--gif-scale", + "1" + ], + "nodes_per_step": 2097152, + "steps": 2048000, + "cost": 4294967296000, + "outputs": [] + }, + { + "id": "D11_naca4412_a12", + "group": "D", + "title": "NACA 4412 (с изгибом), α=12°, Re=1000", + "expect": "ненулевой Cl при α=0 — прямая проверка средней линии профиля", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "4412", + "--size", + "128", + "--body-angle", + "12", + "--nx", + "2048", + "--ny", + "1024", + "--body-x", + "512", + "--re", + "1000", + "--refine", + "1", + "--steps", + "2048000", + "--backend", + "gpu", + "--series-every", + "10", + "--gif", + "naca4412_a12.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "3", + "--gif-scale", + "1" + ], + "nodes_per_step": 2097152, + "steps": 2048000, + "cost": 4294967296000, + "outputs": [] + }, + { + "id": "D12_naca_chord48", + "group": "D", + "title": "NACA 0012, α=8°, сходимость по хорде 48", + "expect": "Cl и Cd сходятся по разрешению хорды", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "48", + "--body-angle", + "8", + "--nx", + "768", + "--ny", + "384", + "--body-x", + "192", + "--re", + "1000", + "--refine", + "1", + "--steps", + "576000", + "--backend", + "gpu", + "--series-every", + "2" + ], + "nodes_per_step": 294912, + "steps": 576000, + "cost": 169869312000, + "outputs": [] + }, + { + "id": "D13_naca_chord96", + "group": "D", + "title": "NACA 0012, α=8°, сходимость по хорде 96", + "expect": "Cl и Cd сходятся по разрешению хорды", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "96", + "--body-angle", + "8", + "--nx", + "1536", + "--ny", + "768", + "--body-x", + "384", + "--re", + "1000", + "--refine", + "1", + "--steps", + "1152000", + "--backend", + "gpu", + "--series-every", + "5" + ], + "nodes_per_step": 1179648, + "steps": 1152000, + "cost": 1358954496000, + "outputs": [] + }, + { + "id": "D14_naca_chord192", + "group": "D", + "title": "NACA 0012, α=8°, сходимость по хорде 192", + "expect": "Cl и Cd сходятся по разрешению хорды", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "192", + "--body-angle", + "8", + "--nx", + "3072", + "--ny", + "1536", + "--body-x", + "768", + "--re", + "1000", + "--refine", + "1", + "--steps", + "2304000", + "--backend", + "gpu", + "--series-every", + "11" + ], + "nodes_per_step": 4718592, + "steps": 2304000, + "cost": 10871635968000, + "outputs": [] + }, + { + "id": "E01_tandem_ld15", + "group": "E", + "title": "Тандем цилиндров, зазор L/D=1.5", + "expect": "при малом зазоре у заднего тела ОТРИЦАТЕЛЬНОЕ сопротивление; переход около 3.5–4", + "backend": "gpu", + "args": [ + "--bodies", + "cylinder:d=48,x=360,y=360; cylinder:d=48,x=432,y=360", + "--nx", + "1440", + "--ny", + "720", + "--re", + "150", + "--refine", + "1", + "--steps", + "1536000", + "--backend", + "gpu", + "--series-every", + "7", + "--gif", + "tandem_ld15.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "2", + "--gif-scale", + "1" + ], + "nodes_per_step": 1036800, + "steps": 1536000, + "cost": 1592524800000, + "outputs": [] + }, + { + "id": "E02_tandem_ld30", + "group": "E", + "title": "Тандем цилиндров, зазор L/D=3.0", + "expect": "при малом зазоре у заднего тела ОТРИЦАТЕЛЬНОЕ сопротивление; переход около 3.5–4", + "backend": "gpu", + "args": [ + "--bodies", + "cylinder:d=48,x=360,y=360; cylinder:d=48,x=504,y=360", + "--nx", + "1440", + "--ny", + "720", + "--re", + "150", + "--refine", + "1", + "--steps", + "1536000", + "--backend", + "gpu", + "--series-every", + "7", + "--gif", + "tandem_ld30.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "2", + "--gif-scale", + "1" + ], + "nodes_per_step": 1036800, + "steps": 1536000, + "cost": 1592524800000, + "outputs": [] + }, + { + "id": "E03_tandem_ld50", + "group": "E", + "title": "Тандем цилиндров, зазор L/D=5.0", + "expect": "при малом зазоре у заднего тела ОТРИЦАТЕЛЬНОЕ сопротивление; переход около 3.5–4", + "backend": "gpu", + "args": [ + "--bodies", + "cylinder:d=48,x=360,y=360; cylinder:d=48,x=600,y=360", + "--nx", + "1440", + "--ny", + "720", + "--re", + "150", + "--refine", + "1", + "--steps", + "1536000", + "--backend", + "gpu", + "--series-every", + "7", + "--gif", + "tandem_ld50.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "2", + "--gif-scale", + "1" + ], + "nodes_per_step": 1036800, + "steps": 1536000, + "cost": 1592524800000, + "outputs": [] + }, + { + "id": "E04_array_sparse", + "group": "E", + "title": "Решётка 4×4 цилиндров, шаг 3D — пористая среда", + "expect": "суммарное сопротивление и структура течения сквозь набор тел", + "backend": "gpu", + "args": [ + "--bodies", + "cylinder:d=24,x=360,y=252.0; cylinder:d=24,x=360,y=324.0; cylinder:d=24,x=360,y=396.0; cylinder:d=24,x=360,y=468.0; cylinder:d=24,x=432,y=252.0; cylinder:d=24,x=432,y=324.0; cylinder:d=24,x=432,y=396.0; cylinder:d=24,x=432,y=468.0; cylinder:d=24,x=504,y=252.0; cylinder:d=24,x=504,y=324.0; cylinder:d=24,x=504,y=396.0; cylinder:d=24,x=504,y=468.0; cylinder:d=24,x=576,y=252.0; cylinder:d=24,x=576,y=324.0; cylinder:d=24,x=576,y=396.0; cylinder:d=24,x=576,y=468.0", + "--nx", + "1440", + "--ny", + "720", + "--re", + "150", + "--refine", + "1", + "--steps", + "576000", + "--backend", + "gpu", + "--series-every", + "2", + "--gif", + "array_sparse.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "2", + "--gif-scale", + "1" + ], + "nodes_per_step": 1036800, + "steps": 576000, + "cost": 597196800000, + "outputs": [] + }, + { + "id": "E05_array_dense", + "group": "E", + "title": "Решётка 4×4 цилиндров, шаг 2D — пористая среда", + "expect": "суммарное сопротивление и структура течения сквозь набор тел", + "backend": "gpu", + "args": [ + "--bodies", + "cylinder:d=24,x=360,y=288.0; cylinder:d=24,x=360,y=336.0; cylinder:d=24,x=360,y=384.0; cylinder:d=24,x=360,y=432.0; cylinder:d=24,x=408,y=288.0; cylinder:d=24,x=408,y=336.0; cylinder:d=24,x=408,y=384.0; cylinder:d=24,x=408,y=432.0; cylinder:d=24,x=456,y=288.0; cylinder:d=24,x=456,y=336.0; cylinder:d=24,x=456,y=384.0; cylinder:d=24,x=456,y=432.0; cylinder:d=24,x=504,y=288.0; cylinder:d=24,x=504,y=336.0; cylinder:d=24,x=504,y=384.0; cylinder:d=24,x=504,y=432.0", + "--nx", + "1440", + "--ny", + "720", + "--re", + "150", + "--refine", + "1", + "--steps", + "576000", + "--backend", + "gpu", + "--series-every", + "2", + "--gif", + "array_dense.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "2", + "--gif-scale", + "1" + ], + "nodes_per_step": 1036800, + "steps": 576000, + "cost": 597196800000, + "outputs": [] + }, + { + "id": "E06_perf_open20", + "group": "E", + "title": "Перфорированная пластина, скважность 0.2", + "expect": "струи в щелях и общее сопротивление; проверка множества тонких тел", + "backend": "gpu", + "args": [ + "--bodies", + "plate:d=40,t=0.12,a=90,x=300,y=60; plate:d=40,t=0.12,a=90,x=300,y=110; plate:d=40,t=0.12,a=90,x=300,y=160; plate:d=40,t=0.12,a=90,x=300,y=210; plate:d=40,t=0.12,a=90,x=300,y=260; plate:d=40,t=0.12,a=90,x=300,y=310; plate:d=40,t=0.12,a=90,x=300,y=360; plate:d=40,t=0.12,a=90,x=300,y=410; plate:d=40,t=0.12,a=90,x=300,y=460", + "--nx", + "1200", + "--ny", + "600", + "--re", + "500", + "--refine", + "1", + "--steps", + "800000", + "--backend", + "gpu", + "--series-every", + "4", + "--gif", + "perf_open20.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 720000, + "steps": 800000, + "cost": 576000000000, + "outputs": [] + }, + { + "id": "E07_perf_open50", + "group": "E", + "title": "Перфорированная пластина, скважность 0.5", + "expect": "струи в щелях и общее сопротивление; проверка множества тонких тел", + "backend": "gpu", + "args": [ + "--bodies", + "plate:d=40,t=0.12,a=90,x=300,y=60; plate:d=40,t=0.12,a=90,x=300,y=140; plate:d=40,t=0.12,a=90,x=300,y=220; plate:d=40,t=0.12,a=90,x=300,y=300; plate:d=40,t=0.12,a=90,x=300,y=380; plate:d=40,t=0.12,a=90,x=300,y=460", + "--nx", + "1200", + "--ny", + "600", + "--re", + "500", + "--refine", + "1", + "--steps", + "800000", + "--backend", + "gpu", + "--series-every", + "4", + "--gif", + "perf_open50.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 720000, + "steps": 800000, + "cost": 576000000000, + "outputs": [] + }, + { + "id": "E08_multielem", + "group": "E", + "title": "Многоэлементный профиль: основной 4412 плюс предкрылок", + "expect": "щель между элементами и раздельные Cl/Cd по телам", + "backend": "gpu", + "args": [ + "--bodies", + "naca:naca=4412,d=200,a=6,x=560,y=512; naca:naca=2412,d=70,a=18,x=430,y=470", + "--nx", + "2048", + "--ny", + "1024", + "--re", + "2000", + "--refine", + "1", + "--steps", + "2400000", + "--backend", + "gpu", + "--series-every", + "12", + "--gif", + "multielem.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "3", + "--gif-scale", + "1" + ], + "nodes_per_step": 2097152, + "steps": 2400000, + "cost": 5033164800000, + "outputs": [] + }, + { + "id": "E09_thin_t05", + "group": "E", + "title": "Сверхтонкая пластина, толщина 1.00 клетки", + "expect": "предел субсеточной границы: тело тоньше клетки; ждём откаты на простой отскок", + "backend": "gpu", + "args": [ + "--shape", + "plate", + "--size", + "50", + "--thickness", + "0.02", + "--body-angle", + "20", + "--nx", + "1200", + "--ny", + "600", + "--re", + "500", + "--refine", + "1", + "--steps", + "1000000", + "--backend", + "gpu", + "--series-every", + "5", + "--gif", + "thin_t05.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 720000, + "steps": 1000000, + "cost": 720000000000, + "outputs": [] + }, + { + "id": "E10_thin_t025", + "group": "E", + "title": "Сверхтонкая пластина, толщина 0.50 клетки", + "expect": "предел субсеточной границы: тело тоньше клетки; ждём откаты на простой отскок", + "backend": "gpu", + "args": [ + "--shape", + "plate", + "--size", + "50", + "--thickness", + "0.01", + "--body-angle", + "20", + "--nx", + "1200", + "--ny", + "600", + "--re", + "500", + "--refine", + "1", + "--steps", + "1000000", + "--backend", + "gpu", + "--series-every", + "5", + "--gif", + "thin_t025.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 720000, + "steps": 1000000, + "cost": 720000000000, + "outputs": [] + }, + { + "id": "E11_wedge", + "group": "E", + "title": "Клин остриём против потока", + "expect": "острая кромка: особая точка геометрии", + "backend": "gpu", + "args": [ + "--shape", + "polygon", + "--poly=-30,-18;36,0;-30,18", + "--size", + "60", + "--nx", + "1200", + "--ny", + "600", + "--re", + "500", + "--refine", + "1", + "--steps", + "1200000", + "--backend", + "gpu", + "--series-every", + "6", + "--gif", + "wedge.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 720000, + "steps": 1200000, + "cost": 864000000000, + "outputs": [] + }, + { + "id": "E12_serrated", + "group": "E", + "title": "Зазубренная задняя кромка", + "expect": "субклеточные зубцы: как их видит граница", + "backend": "gpu", + "args": [ + "--shape", + "polygon", + "--poly=-30,6;-18,-6;-6,6;6,-6;18,6;30,-6;30,-18;-30,-18", + "--size", + "60", + "--nx", + "1200", + "--ny", + "600", + "--re", + "500", + "--refine", + "1", + "--steps", + "1200000", + "--backend", + "gpu", + "--series-every", + "6", + "--gif", + "serrated.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 720000, + "steps": 1200000, + "cost": 864000000000, + "outputs": [] + }, + { + "id": "F01_inlet2d", + "group": "F", + "title": "Отступ до входа 2D", + "expect": "с какого отступа результат перестаёт зависеть от положения входа", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "1056", + "--ny", + "576", + "--body-x", + "96", + "--re", + "150", + "--refine", + "1", + "--steps", + "1152000", + "--backend", + "gpu", + "--series-every", + "5" + ], + "nodes_per_step": 608256, + "steps": 1152000, + "cost": 700710912000, + "outputs": [] + }, + { + "id": "F02_inlet6d", + "group": "F", + "title": "Отступ до входа 6D", + "expect": "с какого отступа результат перестаёт зависеть от положения входа", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "1248", + "--ny", + "576", + "--body-x", + "288", + "--re", + "150", + "--refine", + "1", + "--steps", + "1152000", + "--backend", + "gpu", + "--series-every", + "5" + ], + "nodes_per_step": 718848, + "steps": 1152000, + "cost": 828112896000, + "outputs": [] + }, + { + "id": "F03_inlet16d", + "group": "F", + "title": "Отступ до входа 16D", + "expect": "с какого отступа результат перестаёт зависеть от положения входа", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "1728", + "--ny", + "576", + "--body-x", + "768", + "--re", + "150", + "--refine", + "1", + "--steps", + "1152000", + "--backend", + "gpu", + "--series-every", + "5" + ], + "nodes_per_step": 995328, + "steps": 1152000, + "cost": 1146617856000, + "outputs": [] + }, + { + "id": "F04_outlet5d", + "group": "F", + "title": "Отступ до выхода 5D", + "expect": "с какой длины следа выход перестаёт влиять на силы", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "528", + "--ny", + "576", + "--body-x", + "288", + "--re", + "150", + "--refine", + "1", + "--steps", + "1152000", + "--backend", + "gpu", + "--series-every", + "5" + ], + "nodes_per_step": 304128, + "steps": 1152000, + "cost": 350355456000, + "outputs": [] + }, + { + "id": "F05_outlet15d", + "group": "F", + "title": "Отступ до выхода 15D", + "expect": "с какой длины следа выход перестаёт влиять на силы", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "1008", + "--ny", + "576", + "--body-x", + "288", + "--re", + "150", + "--refine", + "1", + "--steps", + "1152000", + "--backend", + "gpu", + "--series-every", + "5" + ], + "nodes_per_step": 580608, + "steps": 1152000, + "cost": 668860416000, + "outputs": [] + }, + { + "id": "F06_outlet40d", + "group": "F", + "title": "Отступ до выхода 40D", + "expect": "с какой длины следа выход перестаёт влиять на силы", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "2208", + "--ny", + "576", + "--body-x", + "288", + "--re", + "150", + "--refine", + "1", + "--steps", + "1152000", + "--backend", + "gpu", + "--series-every", + "5" + ], + "nodes_per_step": 1271808, + "steps": 1152000, + "cost": 1465122816000, + "outputs": [] + }, + { + "id": "F07_refine1", + "group": "F", + "title": "Вложенный патч ×1", + "expect": "вносит ли связка уровней систематику: Cd и St обязаны совпасть", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "150", + "--refine", + "1", + "--steps", + "768000", + "--backend", + "gpu", + "--series-every", + "3" + ], + "nodes_per_step": 245760, + "steps": 768000, + "cost": 188743680000, + "outputs": [] + }, + { + "id": "F08_refine2", + "group": "F", + "title": "Вложенный патч ×2", + "expect": "вносит ли связка уровней систематику: Cd и St обязаны совпасть", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "150", + "--refine", + "2", + "--steps", + "768000", + "--backend", + "gpu", + "--series-every", + "3" + ], + "nodes_per_step": 501218, + "steps": 768000, + "cost": 384935424000, + "outputs": [] + }, + { + "id": "F09_refine3", + "group": "F", + "title": "Вложенный патч ×3", + "expect": "вносит ли связка уровней систематику: Cd и St обязаны совпасть", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "150", + "--refine", + "3", + "--steps", + "768000", + "--backend", + "gpu", + "--series-every", + "3" + ], + "nodes_per_step": 1106235, + "steps": 768000, + "cost": 849588480000, + "outputs": [] + }, + { + "id": "G01_xt_re150_uniform", + "group": "G", + "title": "x–t диаграмма, Re=150, старт uniform", + "expect": "откуда идёт стартовое возмущение и раскачивается ли продольная мода", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "150", + "--refine", + "1", + "--init", + "uniform", + "--sponge-len", + "0", + "--xt", + "xt_re150_uniform.csv", + "--xt-every", + "200", + "--steps", + "576000", + "--backend", + "gpu", + "--series-every", + "2", + "--gif", + "xt_re150_uniform.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 552960, + "steps": 576000, + "cost": 318504960000, + "outputs": [ + "xt" + ] + }, + { + "id": "G02_xt_re150_rest", + "group": "G", + "title": "x–t диаграмма, Re=150, старт rest", + "expect": "откуда идёт стартовое возмущение и раскачивается ли продольная мода", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "150", + "--refine", + "1", + "--init", + "rest", + "--sponge-len", + "0", + "--xt", + "xt_re150_rest.csv", + "--xt-every", + "200", + "--steps", + "576000", + "--backend", + "gpu", + "--series-every", + "2", + "--gif", + "xt_re150_rest.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 552960, + "steps": 576000, + "cost": 318504960000, + "outputs": [ + "xt" + ] + }, + { + "id": "G03_xt_re2000_uniform", + "group": "G", + "title": "x–t диаграмма, Re=2000, старт uniform", + "expect": "откуда идёт стартовое возмущение и раскачивается ли продольная мода", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "2000", + "--refine", + "1", + "--init", + "uniform", + "--sponge-len", + "0", + "--xt", + "xt_re2000_uniform.csv", + "--xt-every", + "200", + "--steps", + "576000", + "--backend", + "gpu", + "--series-every", + "2", + "--gif", + "xt_re2000_uniform.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 552960, + "steps": 576000, + "cost": 318504960000, + "outputs": [ + "xt" + ] + }, + { + "id": "G04_xt_re2000_rest", + "group": "G", + "title": "x–t диаграмма, Re=2000, старт rest", + "expect": "откуда идёт стартовое возмущение и раскачивается ли продольная мода", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "2000", + "--refine", + "1", + "--init", + "rest", + "--sponge-len", + "0", + "--xt", + "xt_re2000_rest.csv", + "--xt-every", + "200", + "--steps", + "576000", + "--backend", + "gpu", + "--series-every", + "2", + "--gif", + "xt_re2000_rest.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 552960, + "steps": 576000, + "cost": 318504960000, + "outputs": [ + "xt" + ] + }, + { + "id": "G05_xt_re20000_uniform", + "group": "G", + "title": "x–t диаграмма, Re=20000, старт uniform", + "expect": "откуда идёт стартовое возмущение и раскачивается ли продольная мода", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "20000", + "--refine", + "1", + "--init", + "uniform", + "--sponge-len", + "0", + "--xt", + "xt_re20000_uniform.csv", + "--xt-every", + "200", + "--steps", + "576000", + "--backend", + "gpu", + "--series-every", + "2", + "--gif", + "xt_re20000_uniform.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 552960, + "steps": 576000, + "cost": 318504960000, + "outputs": [ + "xt" + ] + }, + { + "id": "G06_xt_re20000_rest", + "group": "G", + "title": "x–t диаграмма, Re=20000, старт rest", + "expect": "откуда идёт стартовое возмущение и раскачивается ли продольная мода", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "20000", + "--refine", + "1", + "--init", + "rest", + "--sponge-len", + "0", + "--xt", + "xt_re20000_rest.csv", + "--xt-every", + "200", + "--steps", + "576000", + "--backend", + "gpu", + "--series-every", + "2", + "--gif", + "xt_re20000_rest.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "1", + "--gif-scale", + "1" + ], + "nodes_per_step": 552960, + "steps": 576000, + "cost": 318504960000, + "outputs": [ + "xt" + ] + }, + { + "id": "G07_sponge0", + "group": "G", + "title": "Губка выхода 0 столбцов при Re=5000", + "expect": "губка — единственное, что реально ест продольную моду", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "5000", + "--refine", + "1", + "--sponge-len", + "0", + "--steps", + "768000", + "--backend", + "gpu", + "--series-every", + "3" + ], + "nodes_per_step": 552960, + "steps": 768000, + "cost": 424673280000, + "outputs": [] + }, + { + "id": "G08_sponge40", + "group": "G", + "title": "Губка выхода 40 столбцов при Re=5000", + "expect": "губка — единственное, что реально ест продольную моду", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "5000", + "--refine", + "1", + "--sponge-len", + "40", + "--steps", + "768000", + "--backend", + "gpu", + "--series-every", + "3" + ], + "nodes_per_step": 552960, + "steps": 768000, + "cost": 424673280000, + "outputs": [] + }, + { + "id": "G09_spongein0", + "group": "G", + "title": "Губка входа 0 столбцов при Re=5000", + "expect": "помогает ли гасить волну до отражения от входа", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "5000", + "--refine", + "1", + "--sponge-in", + "0", + "--steps", + "768000", + "--backend", + "gpu", + "--series-every", + "3" + ], + "nodes_per_step": 552960, + "steps": 768000, + "cost": 424673280000, + "outputs": [] + }, + { + "id": "G10_spongein40", + "group": "G", + "title": "Губка входа 40 столбцов при Re=5000", + "expect": "помогает ли гасить волну до отражения от входа", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "5000", + "--refine", + "1", + "--sponge-in", + "40", + "--steps", + "768000", + "--backend", + "gpu", + "--series-every", + "3" + ], + "nodes_per_step": 552960, + "steps": 768000, + "cost": 424673280000, + "outputs": [] + }, + { + "id": "G11_limit_re10000", + "group": "G", + "title": "Предел устойчивости, Re=10000", + "expect": "где именно гибнет счёт; часть этих прогонов обязана развалиться", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "10000", + "--refine", + "1", + "--steps", + "384000", + "--backend", + "gpu" + ], + "nodes_per_step": 552960, + "steps": 384000, + "cost": 212336640000, + "outputs": [] + }, + { + "id": "G12_limit_re50000", + "group": "G", + "title": "Предел устойчивости, Re=50000", + "expect": "где именно гибнет счёт; часть этих прогонов обязана развалиться", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "50000", + "--refine", + "1", + "--steps", + "384000", + "--backend", + "gpu" + ], + "nodes_per_step": 552960, + "steps": 384000, + "cost": 212336640000, + "outputs": [] + }, + { + "id": "G13_limit_re200000", + "group": "G", + "title": "Предел устойчивости, Re=200000", + "expect": "где именно гибнет счёт; часть этих прогонов обязана развалиться", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "200000", + "--refine", + "1", + "--steps", + "384000", + "--backend", + "gpu" + ], + "nodes_per_step": 552960, + "steps": 384000, + "cost": 212336640000, + "outputs": [] + }, + { + "id": "G14_lifetime", + "group": "G", + "title": "Тест на время жизни: 5·10⁶ шагов", + "expect": "не уплывает ли ⟨ρ⟩ и не деградирует ли решение на очень длинной дистанции", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "48", + "--nx", + "960", + "--ny", + "576", + "--body-x", + "192", + "--re", + "150", + "--refine", + "1", + "--steps", + "10000000", + "--backend", + "gpu", + "--series-every", + "50", + "--gif", + "lifetime.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "2", + "--gif-scale", + "1" + ], + "nodes_per_step": 552960, + "steps": 10000000, + "cost": 5529600000000, + "outputs": [] + }, + { + "id": "H01_sym_cyl", + "group": "H", + "title": "Симметрия: цилиндр при α=0 без возмущения", + "expect": "Cl и Cm обязаны остаться ≈0 — это внутреннее свойство схемы, не литература", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "40", + "--refine", + "1", + "--pert-amp", + "0", + "--steps", + "512000", + "--backend", + "gpu", + "--series-every", + "2" + ], + "nodes_per_step": 245760, + "steps": 512000, + "cost": 125829120000, + "outputs": [] + }, + { + "id": "H02_sym_foil", + "group": "H", + "title": "Симметрия: NACA 0012 при α=0 без возмущения", + "expect": "Cl ≈ 0 у симметричного профиля под нулевым углом", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "64", + "--body-angle", + "0", + "--nx", + "1024", + "--ny", + "512", + "--body-x", + "256", + "--re", + "500", + "--refine", + "1", + "--pert-amp", + "0", + "--steps", + "768000", + "--backend", + "gpu", + "--series-every", + "3" + ], + "nodes_per_step": 524288, + "steps": 768000, + "cost": 402653184000, + "outputs": [] + }, + { + "id": "H03_mirror_plus", + "group": "H", + "title": "Зеркальность: NACA 0012 при α=8°", + "expect": "ряды обязаны зеркалиться: Cl меняет знак, Cd совпадает", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "64", + "--body-angle", + "8", + "--nx", + "1024", + "--ny", + "512", + "--body-x", + "256", + "--re", + "500", + "--refine", + "1", + "--steps", + "768000", + "--backend", + "gpu", + "--series-every", + "3" + ], + "nodes_per_step": 524288, + "steps": 768000, + "cost": 402653184000, + "outputs": [] + }, + { + "id": "H04_mirror_minus", + "group": "H", + "title": "Зеркальность: NACA 0012 при α=-8°", + "expect": "ряды обязаны зеркалиться: Cl меняет знак, Cd совпадает", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--size", + "64", + "--body-angle", + "-8", + "--nx", + "1024", + "--ny", + "512", + "--body-x", + "256", + "--re", + "500", + "--refine", + "1", + "--steps", + "768000", + "--backend", + "gpu", + "--series-every", + "3" + ], + "nodes_per_step": 524288, + "steps": 768000, + "cost": 402653184000, + "outputs": [] + }, + { + "id": "H05_mach02", + "group": "H", + "title": "Зависимость от числа Маха, u_lat=0.02", + "expect": "результат обязан сходиться при Ma→0; расхождение и есть сжимаемостная ошибка", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "150", + "--refine", + "1", + "--u-lat", + "0.02", + "--steps", + "1280000", + "--backend", + "gpu", + "--series-every", + "6" + ], + "nodes_per_step": 245760, + "steps": 1280000, + "cost": 314572800000, + "outputs": [] + }, + { + "id": "H06_mach05", + "group": "H", + "title": "Зависимость от числа Маха, u_lat=0.05", + "expect": "результат обязан сходиться при Ma→0; расхождение и есть сжимаемостная ошибка", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "150", + "--refine", + "1", + "--u-lat", + "0.05", + "--steps", + "512000", + "--backend", + "gpu", + "--series-every", + "2" + ], + "nodes_per_step": 245760, + "steps": 512000, + "cost": 125829120000, + "outputs": [] + }, + { + "id": "H07_mach10", + "group": "H", + "title": "Зависимость от числа Маха, u_lat=0.1", + "expect": "результат обязан сходиться при Ma→0; расхождение и есть сжимаемостная ошибка", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "150", + "--refine", + "1", + "--u-lat", + "0.1", + "--steps", + "256000", + "--backend", + "gpu" + ], + "nodes_per_step": 245760, + "steps": 256000, + "cost": 62914560000, + "outputs": [] + }, + { + "id": "H08_prec_cpu", + "group": "H", + "title": "Точность: одинаковая постановка на cpu", + "expect": "расхождение f32 против f64 в зависимости от длины прогона", + "backend": "cpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "150", + "--refine", + "1", + "--steps", + "1024000", + "--backend", + "cpu", + "--series-every", + "5" + ], + "nodes_per_step": 245760, + "steps": 1024000, + "cost": 251658240000, + "outputs": [] + }, + { + "id": "H09_prec_gpu", + "group": "H", + "title": "Точность: одинаковая постановка на gpu", + "expect": "расхождение f32 против f64 в зависимости от длины прогона", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "32", + "--nx", + "640", + "--ny", + "384", + "--body-x", + "128", + "--re", + "150", + "--refine", + "1", + "--steps", + "1024000", + "--backend", + "gpu", + "--series-every", + "5" + ], + "nodes_per_step": 245760, + "steps": 1024000, + "cost": 251658240000, + "outputs": [] + }, + { + "id": "I01_fine_cylinder", + "group": "I", + "title": "Сверхмелкая сетка 4096×2048, cylinder, D=256", + "expect": "держится ли субсеточная граница на предельном разрешении для этой формы", + "backend": "gpu", + "args": [ + "--shape", + "cylinder", + "--size", + "256", + "--nx", + "4096", + "--ny", + "2048", + "--body-x", + "820", + "--re", + "1000", + "--refine", + "1", + "--steps", + "3072000", + "--backend", + "gpu", + "--series-every", + "15", + "--gif", + "fine_cylinder.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "6", + "--gif-scale", + "1" + ], + "nodes_per_step": 8388608, + "steps": 3072000, + "cost": 25769803776000, + "outputs": [] + }, + { + "id": "I02_fine_square", + "group": "I", + "title": "Сверхмелкая сетка 4096×2048, square, D=256", + "expect": "держится ли субсеточная граница на предельном разрешении для этой формы", + "backend": "gpu", + "args": [ + "--shape", + "square", + "--size", + "256", + "--nx", + "4096", + "--ny", + "2048", + "--body-x", + "820", + "--re", + "1000", + "--refine", + "1", + "--steps", + "3072000", + "--backend", + "gpu", + "--series-every", + "15", + "--gif", + "fine_square.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "6", + "--gif-scale", + "1" + ], + "nodes_per_step": 8388608, + "steps": 3072000, + "cost": 25769803776000, + "outputs": [] + }, + { + "id": "I03_fine_diamond", + "group": "I", + "title": "Сверхмелкая сетка 4096×2048, diamond, D=256", + "expect": "держится ли субсеточная граница на предельном разрешении для этой формы", + "backend": "gpu", + "args": [ + "--shape", + "diamond", + "--size", + "256", + "--nx", + "4096", + "--ny", + "2048", + "--body-x", + "820", + "--re", + "1000", + "--refine", + "1", + "--steps", + "3072000", + "--backend", + "gpu", + "--series-every", + "15", + "--gif", + "fine_diamond.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "6", + "--gif-scale", + "1" + ], + "nodes_per_step": 8388608, + "steps": 3072000, + "cost": 25769803776000, + "outputs": [] + }, + { + "id": "I04_fine_ellipse", + "group": "I", + "title": "Сверхмелкая сетка 4096×2048, ellipse, D=256", + "expect": "держится ли субсеточная граница на предельном разрешении для этой формы", + "backend": "gpu", + "args": [ + "--shape", + "ellipse", + "--thickness", + "0.35", + "--size", + "256", + "--nx", + "4096", + "--ny", + "2048", + "--body-x", + "820", + "--re", + "1000", + "--refine", + "1", + "--steps", + "3072000", + "--backend", + "gpu", + "--series-every", + "15", + "--gif", + "fine_ellipse.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "6", + "--gif-scale", + "1" + ], + "nodes_per_step": 8388608, + "steps": 3072000, + "cost": 25769803776000, + "outputs": [] + }, + { + "id": "I05_fine_naca", + "group": "I", + "title": "Сверхмелкая сетка 4096×2048, naca, D=256", + "expect": "держится ли субсеточная граница на предельном разрешении для этой формы", + "backend": "gpu", + "args": [ + "--shape", + "naca", + "--naca", + "0012", + "--body-angle", + "8", + "--size", + "256", + "--nx", + "4096", + "--ny", + "2048", + "--body-x", + "820", + "--re", + "1000", + "--refine", + "1", + "--steps", + "3072000", + "--backend", + "gpu", + "--series-every", + "15", + "--gif", + "fine_naca.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "6", + "--gif-scale", + "1" + ], + "nodes_per_step": 8388608, + "steps": 3072000, + "cost": 25769803776000, + "outputs": [] + }, + { + "id": "I06_fine_triangle", + "group": "I", + "title": "Сверхмелкая сетка 4096×2048, triangle, D=256", + "expect": "держится ли субсеточная граница на предельном разрешении для этой формы", + "backend": "gpu", + "args": [ + "--shape", + "triangle", + "--size", + "256", + "--nx", + "4096", + "--ny", + "2048", + "--body-x", + "820", + "--re", + "1000", + "--refine", + "1", + "--steps", + "3072000", + "--backend", + "gpu", + "--series-every", + "15", + "--gif", + "fine_triangle.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "6", + "--gif-scale", + "1" + ], + "nodes_per_step": 8388608, + "steps": 3072000, + "cost": 25769803776000, + "outputs": [] + }, + { + "id": "I07_fine_plate", + "group": "I", + "title": "Сверхмелкая сетка 4096×2048, plate, D=256", + "expect": "держится ли субсеточная граница на предельном разрешении для этой формы", + "backend": "gpu", + "args": [ + "--shape", + "plate", + "--thickness", + "0.08", + "--body-angle", + "25", + "--size", + "256", + "--nx", + "4096", + "--ny", + "2048", + "--body-x", + "820", + "--re", + "1000", + "--refine", + "1", + "--steps", + "3072000", + "--backend", + "gpu", + "--series-every", + "15", + "--gif", + "fine_plate.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "6", + "--gif-scale", + "1" + ], + "nodes_per_step": 8388608, + "steps": 3072000, + "cost": 25769803776000, + "outputs": [] + }, + { + "id": "I08_fine_wedge", + "group": "I", + "title": "Сверхмелкая сетка 4096×2048, wedge, D=256", + "expect": "держится ли субсеточная граница на предельном разрешении для этой формы", + "backend": "gpu", + "args": [ + "--shape", + "polygon", + "--poly=-128,-72;154,0;-128,72", + "--size", + "256", + "--nx", + "4096", + "--ny", + "2048", + "--body-x", + "820", + "--re", + "1000", + "--refine", + "1", + "--steps", + "3072000", + "--backend", + "gpu", + "--series-every", + "15", + "--gif", + "fine_wedge.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "6", + "--gif-scale", + "1" + ], + "nodes_per_step": 8388608, + "steps": 3072000, + "cost": 25769803776000, + "outputs": [] + }, + { + "id": "I09_fine_multi", + "group": "I", + "title": "Сверхмелкая сетка, композиция из трёх тел", + "expect": "взаимодействие следов на предельном разрешении", + "backend": "gpu", + "args": [ + "--bodies", + "cylinder:d=200,x=800,y=1024; cylinder:d=200,x=1400,y=1024; naca:naca=4412,d=300,a=10,x=2200,y=1024", + "--nx", + "4096", + "--ny", + "2048", + "--re", + "1000", + "--refine", + "1", + "--steps", + "3072000", + "--backend", + "gpu", + "--series-every", + "15", + "--gif", + "fine_multi.gif", + "--gif-field", + "vorticity", + "--gif-every", + "auto", + "--gif-fps", + "10", + "--gif-speed", + "1", + "--gif-downsample", + "6", + "--gif-scale", + "1" + ], + "nodes_per_step": 8388608, + "steps": 3072000, + "cost": 25769803776000, + "outputs": [] + } + ] +} \ No newline at end of file diff --git a/docs/theory/2d_solver/docker-compose.yml b/docs/theory/2d_solver/docker-compose.yml new file mode 100644 index 0000000..a632be0 --- /dev/null +++ b/docs/theory/2d_solver/docker-compose.yml @@ -0,0 +1,27 @@ +# Запуск кампании на сервере с NVIDIA одной командой: +# docker compose run --rm kbc2d --calibrate # сначала убедиться, что GPU виден +# docker compose up -d # кампания в фоне +# docker compose logs -f # смотреть ход +# +# Результаты складываются в ./out на хосте. + +services: + kbc2d: + build: . + image: kbc2d + container_name: kbc2d + volumes: + - ./out:/work/bench/out + environment: + # Без graphics NVIDIA Container Toolkit не подложит Vulkan-ICD и wgpu не увидит карту. + NVIDIA_DRIVER_CAPABILITIES: compute,utility,graphics + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + # Кампания идёт десятки часов и переживает перезапуск: --resume пропускает готовое. + command: ["--resume"] + restart: "no"