Пристеночная функция Spalding, многоуровневый домен, группы J и K
Решатель: - --wall-function spalding поверх bouzidi/grad/hrr: моментные схемы берут модельную скорость узла и u_τ²/ν в тензор давлений, Bouzidi — скольжение стенки, согласованное по напряжению (в вязком подслое тождественно ноль); - --levels: цепочка вложенных уровней с рекурсивным шагом на CPU и GPU, одиночный патч — её частный случай, прежние постановки побитово те же; - --sponge-side, β по узлам; в сводке y⁺, cl_mean, rho_probe_rms. - Исправлено: на GPU при --refine >= 3 сила лишних подшагов терялась за краем буфера, Cd занижался в r/2 раза. Кампания: группы J (65, схемы стенки с функцией) и K (25, внешний домен), 205 прогонов на 5 долей по ≈27 ч; bench/compare.py сводит J и K против эталонов; parity.py сверяет Spalding и три уровня. Образ 1.4.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+124
-11
@@ -311,8 +311,11 @@ c_s²(1/(γβ) − ½) и, поскольку измеренная ⟨γ⟩ ≈
|
||||
низкими числами Рейнольдса, поскольку на границе возникают паразитные скачки») и естественная
|
||||
форма для подвижных стенок: скорость стенки входит в целевые значения, а не отдельной поправкой.
|
||||
В здешней канальной постановке преимущества по устойчивости воспроизвести не удалось: при росте
|
||||
Re обе модели теряют счёт на одном и том же значении (Re ≈ 5·10⁴ при теле в 16 клеток), то есть
|
||||
ограничивает не стенка, а что-то другое — вероятнее всего Zou–He при τ → ½.
|
||||
Re обе модели теряют счёт на одном и том же значении, то есть ограничивает не стенка, а
|
||||
что-то другое — вероятнее всего Zou–He при τ → ½. (Сама оценка «Re ≈ 5·10⁴ при теле в 16
|
||||
клеток» была сделана по коротким прогонам; длинные дают предел τ − ½ ≈ 6·10⁻⁴, то есть при
|
||||
D = 16 уже Re ≈ 4000, — см. раздел «Дальше». Вывод «стенка не ограничивает» он не меняет:
|
||||
Bouzidi и HRR со Spalding разваливаются при одних и тех же Re.)
|
||||
|
||||
**Умолчание — `hrr`, и это решение временное.** Оно принято по устройству схемы (третий порядок
|
||||
Эрмита фильтрует высокочастотный мусор у стенки, чего обрыв на Π не делает), а не по здешним
|
||||
@@ -326,6 +329,59 @@ Re обе модели теряют счёт на одном и том же зн
|
||||
это только потому, что две модели дали побитово одинаковый результат там, где обязаны были
|
||||
разойтись.
|
||||
|
||||
### Пристеночная функция Spalding
|
||||
|
||||
Ключ `--wall-function none|spalding` (умолчание `none` — всё прежнее не меняется побитово)
|
||||
ставит поверх `bouzidi`, `grad` или `hrr` закон стенки Spalding (1961): одна формула на весь
|
||||
пристеночный слой, от вязкого подслоя (u⁺ = y⁺) до логарифмической зоны, κ = 0.41, B = 5.2.
|
||||
С `staircase` не сочетается: ступенька не знает, где стенка.
|
||||
|
||||
Как устроено:
|
||||
|
||||
- у каждого граничного узла есть нормаль (градиент SDF), расстояние до стенки y_w и точка
|
||||
отбора скорости на одну клетку дальше по нормали (Malaspinas, Sagaut 2014); скорость там —
|
||||
билинейно по полю на момент t. Геометрия строится один раз в `cpu::Geom` и загружается на
|
||||
GPU как есть;
|
||||
- по касательной скорости в точке отбора Ньютоном находится u_τ, по нему — модельная скорость
|
||||
узла u_node = u_τ·u⁺(y⁺_w) и напряжение τ_w = ρu_τ²;
|
||||
- **`grad`/`hrr`**: касательная составляющая целевой скорости (B 1) заменяется на u_node, а в
|
||||
тензоре давлений нормальная производная касательной скорости — на u_τ²/ν. Плотность (B 3) и
|
||||
сборка популяций не меняются;
|
||||
- **`bouzidi`**: стенка получает скорость скольжения u_s = u_node − u_τ²·y_w/ν и входит в
|
||||
отскок поправкой подвижной стенки. Скольжение согласовано по **напряжению**: отскок передаёт
|
||||
стенке ρν(u_node − u_s)/y_w = τ_w. В вязком подслое u⁺ = y⁺, и u_s тождественно ноль; в
|
||||
буферной и логарифмической зонах u_s < 0 — стенка «отступает», добирая трение до τ_w.
|
||||
Модуль скольжения зажат скоростью в точке отбора.
|
||||
|
||||
Первая версия согласовывала скольжение по **скорости** (прямая через стенку, узел и точку
|
||||
отбора). В решателе без турбулентной вязкости это даёт большое положительное скольжение —
|
||||
стенка становится почти свободной: цилиндр Re = 10⁴, D = 16 выдал Cd = 0.016. Вторая брала
|
||||
разрешённую скорость узла вместо модельной и при Re = 20 расходилась с прилипанием на 2 %:
|
||||
у искривлённой стенки узел не лежит на прямой от стенки к точке отбора. Нынешняя формула
|
||||
свободна от обоих изъянов.
|
||||
|
||||
**Сведение к прилипанию при малом y⁺** (цилиндр Re = 20, D = 16, 320×192, y⁺ ≈ 0.25, GPU):
|
||||
|
||||
| модель | прилипание | Spalding | разница |
|
||||
|---|---|---|---|
|
||||
| `bouzidi` | 2.38252 | 2.38252 | 0 (до 7-го знака) |
|
||||
| `grad` | 2.38774 | 2.38989 | +0.09 % |
|
||||
| `hrr` | 2.38835 | 2.39053 | +0.09 % |
|
||||
|
||||
У моментных схем остаточные 0.09 % — от того, что целевая скорость строится теперь по нормали
|
||||
к стенке, а не усреднением по линкам (B 1).
|
||||
|
||||
**Закон стенки описывает турбулентный слой, а при умеренных Re он ламинарный.** На цилиндре при
|
||||
Re = 2000 (D = 16, y⁺ ≈ 5, местами до 19, 16 тыс. шагов) функция заметно двигает только Bouzidi:
|
||||
Cd 2.10 против 1.56 без неё; у `grad` 1.59 против 1.65, у `hrr` 1.70 против 1.69. Bouzidi
|
||||
навязывает модельное трение через обмен импульсом жёстко, моментные схемы — через тензор
|
||||
давлений одного слоя узлов, мягче. Какая схема ближе к эталону — меряет группа J кампании.
|
||||
|
||||
Каждый прогон канала пишет в сводку y⁺ первого узла на самом тонком уровне (`yplus_mean`,
|
||||
`yplus_max`, долю узлов с y⁺ > 30 `yplus_frac_log`, `u_tau_mean`) — два десятка снимков во
|
||||
второй половине прогона, с функцией и без неё. Без y⁺ сравнение схем стенки не
|
||||
интерпретировать.
|
||||
|
||||
### Паритет бэкендов и согласованность уровней
|
||||
|
||||
CPU (f64) и GPU (f32) на одной постановке совпадают до 4–5 значащих цифр шаг в шаг: ⟨ρ⟩
|
||||
@@ -335,6 +391,18 @@ CPU (f64) и GPU (f32) на одной постановке совпадают
|
||||
Один и тот же случай с патчем ×2 и вовсе без измельчения (`--refine 1`) даёт St 0.1951 против
|
||||
0.1970 и ⟨Cd⟩ 1.956 против 1.943 — связка уровней систематики не вносит.
|
||||
|
||||
**Исправлено: сила на GPU при `--refine 3` и выше.** Рабочие слоты силы в буфере итогов были
|
||||
рассчитаны ровно на два подшага тонкого уровня; сила третьего и следующих писалась за край
|
||||
буфера и терялась, а делилась сумма всё равно на r. Cd на GPU выходил занижен в r/2 раза:
|
||||
два тела при `--refine 3` — 1.316 против 1.972 на CPU. Теперь слотов столько, сколько подшагов
|
||||
самого тонкого уровня на шаг L0: 1.9733 против 1.9716. Это затрагивало прогон `F09_refine3`
|
||||
кампании и любой ручной запуск с `--refine ≥ 3` на GPU; при `--refine 2` и без патча
|
||||
результат прежний побитово.
|
||||
|
||||
`bench/parity.py` сверяет CPU и GPU на четырёх случаях: Тейлор–Грин, стационарный цилиндр,
|
||||
цилиндр с `grad` + Spalding и три уровня ×2×2. Замерено на Iris Xe (Vulkan): Cd совпадает до
|
||||
1.7·10⁻⁵ (Spalding) и 7.3·10⁻⁵ (три уровня), y⁺ — до 6.4·10⁻⁵.
|
||||
|
||||
**Предел на размер сетки снят.** У GPU есть жёсткий предел `maxComputeWorkgroupsPerDimension`
|
||||
= 65535, а диспетчеризация была одномерной: при 64 узлах на рабочую группу это упирало сетку в
|
||||
4.2 миллиона узлов, то есть примерно 2048×2048. Всё, что крупнее, падало ошибкой валидации —
|
||||
@@ -470,15 +538,56 @@ rms Cl 0.81 вместо 0.40 — то есть 75-процентная прод
|
||||
просто переносится во времени: поток всё равно обязан разогнаться вокруг тела, и энергия этого
|
||||
переходного процесса задана физикой, а не гладкостью начального поля. Умолчание — 0.
|
||||
|
||||
## Многоуровневый домен
|
||||
|
||||
`--levels "r:up,down,side; r:up,down,side; …"` строит цепочку вложенных уровней вместо одного
|
||||
патча: L0 — самый грубый, на весь домен, каждый следующий в r раз тоньше родителя и лежит
|
||||
внутри него. `up`, `down`, `side` — отступы границ уровня от центра тела в его диаметрах
|
||||
(вверх по потоку, вниз, в стороны). Так строится внешний домен с грубыми буферами: большой и
|
||||
дешёвый L0, к телу сетка мельчает ступенями. `--levels` перекрывает `--refine`/`--patch`;
|
||||
одиночный патч — частный случай цепочки из одного звена, и прежние постановки дают **побитово
|
||||
тот же** результат на обоих бэкендах (сверено на пяти постановках: без патча, ×2, ×3, два тела,
|
||||
периодический случай; единственное исключение — исправленная сила при `--refine 3` на GPU).
|
||||
|
||||
```sh
|
||||
# L0 в 4 раза грубее тела; ступени ×2: 4D вверх, 15D вниз, ±4D, затем 1.5D, 10D следа, ±2D
|
||||
./target/release/kbc2d --shape cylinder --size 8 --nx 480 --ny 320 --body-x 160 --re 150 \
|
||||
--levels "2:4,15,4; 2:1.5,10,2" --wall bouzidi --steps 96000 --backend gpu
|
||||
```
|
||||
|
||||
Важно: `--size`, `--nx`, `--ny`, `--steps` и Re задаются **в клетках и шагах L0**. Тело в 8
|
||||
клеток L0 при цепочке ×2×2 — это 32 клетки на самом тонком уровне; отчёт печатает D и τ
|
||||
каждого уровня. τ растёт к тонким уровням (τ_k = r_k(τ_{k−1} − ½) + ½, ν одна на всех), так
|
||||
что ближе всего к пределу устойчивости τ → ½ именно L0.
|
||||
|
||||
Шаг рекурсивный: уровень делает шаг, затем его потомок — r подшагов с временной интерполяцией
|
||||
рамки, затем рестрикция обратно; сила снимается на самом тонком уровне на каждом его подшаге.
|
||||
Стенки канала, вход, выход и губки есть только у L0. На GPU это та же рекурсия в одном
|
||||
командном буфере; ядра связки не менялись.
|
||||
|
||||
Проверено: однородный поток без тела — неподвижная точка через три уровня с точностью 10⁻¹²;
|
||||
стационарный цилиндр Re = 20 при одном скачке ×4 и при цепочке ×2×2 с тем же разрешением тела
|
||||
даёт Cd 2.2104 и 2.2156 (0.24 %); CPU и GPU на трёх уровнях совпадают до 7.3·10⁻⁵ по Cd.
|
||||
|
||||
**Боковые губки** `--sponge-side N` — N рядов L0 у верхней и нижней стенок с той же плавной
|
||||
надбавкой вязкости, что у входа и выхода; β теперь поле по узлам, а не по столбцам. Нужны,
|
||||
чтобы отличать отражения от боковых стенок от отражений на стыках уровней.
|
||||
|
||||
В сводку добавлены `levels` (цепочка с размерами), `nodes_per_step` (обновлений узлов за шаг L0
|
||||
по всем уровням), `wall_function`, `sponge_side`, `rho_probe_rms` (пульсация плотности в зонде
|
||||
во второй половине — мера паразитной акустики) и `cl_mean`; в `series.csv` — столбец
|
||||
`rho_probe`.
|
||||
|
||||
## Валидационная кампания
|
||||
|
||||
`bench/` — 115 прогонов на ≈90 машинных часов, разложенных по девяти группам: эталоны
|
||||
`bench/` — 205 прогонов на ≈101 машинный час, разложенных по одиннадцати группам: эталоны
|
||||
первоисточников, цилиндр против литературы, модели стенки, профили крыла, сложная и
|
||||
множественная геометрия, границы домена, старт и время жизни, внутренние инварианты,
|
||||
сверхмелкие сетки до 4096×2048. Из них 108 идут на GPU (≈87 часов) и 7 — на CPU (≈3 часа,
|
||||
исследования сходимости группы A). Каждый прогон кладёт в собственную папку `cmd.txt`,
|
||||
`log.txt`, `report.txt`, `series.csv` и `summary.json`; гифку на всю свою длительность
|
||||
пишут **59 прогонов из 115**, остальным `--gif` не передаётся вовсе.
|
||||
сверхмелкие сетки до 4096×2048, схемы стенки с пристеночной функцией (J) и внешний домен с
|
||||
грубыми буферами (K). Из них 198 идут на GPU и 7 — на CPU (≈3 часа, исследования сходимости
|
||||
группы A). Каждый прогон кладёт в собственную папку `cmd.txt`, `log.txt`, `report.txt`,
|
||||
`series.csv` и `summary.json`; гифку на всю свою длительность пишут **59 прогонов из 205** —
|
||||
группы J и K гифок не пишут. Итоги J и K сводит `bench/compare.py`.
|
||||
|
||||
```sh
|
||||
cd bench
|
||||
@@ -497,7 +606,7 @@ python preflight.py # каждый сценарий старту
|
||||
|
||||
## Развёртывание (Docker)
|
||||
|
||||
Собранный образ опубликован: **`notbigghost/kbc2d:1.3.0`** (он же `latest`, платформа `linux/amd64`).
|
||||
Собранный образ опубликован: **`notbigghost/kbc2d:1.4.0`** (он же `latest`, платформа `linux/amd64`).
|
||||
Исходники на сервере не нужны — достаточно перенести туда один файл
|
||||
`docker-compose.server.yml`:
|
||||
|
||||
@@ -525,7 +634,7 @@ docker run --rm --gpus all kbc2d --calibrate
|
||||
|
||||
### Пять долей на пяти машинах
|
||||
|
||||
Кампания разложена на **пять равных по времени долей** (≈24 ч каждая, поле `shard` в
|
||||
Кампания разложена на **пять равных по времени долей** (≈27 ч каждая, поле `shard` в
|
||||
`scenarios.json`), чтобы считать её одновременно на пяти чужих ПК с видеокартой под Windows
|
||||
или WSL2. На машину переносятся два файла — `docker-compose.shards.yml` и `shard.bat` — и
|
||||
выполняется `shard.bat 3` (или `docker compose -f docker-compose.shards.yml up -d shard3`).
|
||||
@@ -618,7 +727,11 @@ GPU не найден. Согласие даётся явно, переменн
|
||||
стартовый импульс от появления тела в потоке;
|
||||
- подвижные и вращающиеся тела: GMEM уже записан в галилей-инвариантной форме и принимает
|
||||
скорость стенки на линке, но подача этой скорости не подключена;
|
||||
- разбор нерешённых 12% по Cd и предела устойчивости Re ≈ 5·10⁴ — обе задачи вынесены в
|
||||
кампанию (группы B и G), выводы делать по её данным;
|
||||
- разбор нерешённых 12% по Cd и предела устойчивости — обе задачи вынесены в кампанию
|
||||
(группы B и G), выводы делать по её данным. Оценка предела Re ≈ 5·10⁴ при D = 16 была сделана
|
||||
по коротким прогонам и не подтвердилась: в длинных (цилиндр 20D×12D, Bouzidi) Re = 10⁴
|
||||
разваливается при D = 16 и 32 на 13 и 27 тысячах шагов, а держится при τ − ½ ≥ 6·10⁻⁴
|
||||
(Re = 2000 при D = 8, 5000 при D = 32, 10⁴ при D = 64); пристеночная функция предел не
|
||||
сдвигает;
|
||||
- больше четырёх тел в домене: сейчас сила считается по четырём вёдрам (`MAX_BODY_BUCKETS`),
|
||||
геометрия при этом собирается из любого числа тел, но силы сверх четвёртого сливаются вместе.
|
||||
|
||||
Reference in New Issue
Block a user