e75b404f8adb3e5926340ea1bc46b97d30b447aa
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e75b404f8a |
Опубликовать образ в реестр и добавить compose для сервера
Образ собран под linux/amd64 и выложен как notbigghost/kbc2d — теги 1.0.0,
latest и
|
||
|
|
8d17bc8014 |
Валидационная кампания на 115 прогонов и Docker-образ для сервера
bench/ — список сценариев (порождается gen_scenarios.py, а не правится руками), драйверы под Linux и Windows, предполётная проверка и описание. Девять групп: эталоны первоисточников, цилиндр против литературы, модели стенки и субсеточность, профили крыла, сложная и множественная геометрия, границы домена, старт и время жизни, внутренние инварианты, сверхмелкие сетки до 4096×2048. Стоимость планируется в обновлениях узлов — единственной переносимой между машинами мере; в часы драйвер переводит её по фактическим MLUPS, которые замеряет на месте (--calibrate). Итого 3.76e14 обновлений, ≈90 часов на 4070 Ti. Гифки пишутся на всю длительность прогона в реальном времени, 10 кадр/с. Число кадров этим задано жёстко, поэтому размер регулируется только кадром: замерено 0.103 байта на пиксель после LZW, отсюда бюджет кадры×пиксели с нижней границей по ширине. Итог 4.4 ГБ, самый тяжёлый файл 226 МБ. preflight.py гоняет каждый сценарий на два шага. Окупился сразу: поймал, что вся группа сверхмелких сеток падала на пределе GPU, а девять прогонов группы F передавали --body-x дважды. Оба отказа проявились бы только на сервере, часов через двадцать после старта кампании. Docker: двухстадийная сборка, в образе libvulkan1 и vulkan-tools. NVIDIA Container Toolkit подкладывает Vulkan-ICD только при graphics в NVIDIA_DRIVER_CAPABILITIES — без него wgpu не увидит карту, поэтому capabilities прописаны в образе, а vulkaninfo лежит внутрь для проверки. ENTRYPOINT — драйвер кампании, CMD по умолчанию --dry-run, чтобы случайный docker run не запустил сточасовую задачу. Проверено локально: образ собирается, смоук проходит с монтированием результатов на хост, физика совпадает с хостовой до последней цифры, --backend gpu без карты отказывает явно. GPU-путь в контейнере проверяется только на машине с картой. В README поправлено разделение вкладов в продольную акустику: измерением 2×2 показано, что 74.9% → 1.1% даёт ОДНОРОДНЫЙ СТАРТ, причём при полностью выключенной губке, а губка снимает лишь остаток (1.12% → 0.83% при утроении длины). При старте из покоя губка не помогает совсем — у стоячей четвертьволновой моды там узел давления, где вязкость её не трогает. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
26a8475396 |
Снять предел GPU на размер сетки и молчаливые подмены модели стенки
Диспетчеризация была одномерной, а у GPU жёсткий предел 65535 рабочих групп на измерение: при 64 узлах на группу это упирало сетку в 4.2 млн узлов, то есть примерно 2048×2048. Всё, что крупнее, не считало медленно, а падало ошибкой валидации — то есть вся группа сверхмелких сеток кампании (4096×2048 и 4096×4096) не запустилась бы вовсе. Теперь сетка рабочих групп двумерная, а линейный индекс собирается в шейдере через lin()/wlin(); отображение «группа → узлы» при этом остаётся ровно линейным, поэтому редукции и буфер частичных сумм не потребовали изменений, кроме отсечения хвостовых групп. Проверено до 4096×4096; паритет с CPU не сдвинулся ни в одной цифре (Cd 2.4650 против 2.4649, как и до правки). --wall staircase на GPU молча считался по Bouzidi: ветка выбиралась по is_moment_based(), и ступенчатая модель попадала в ту же ветку, что интерполированный отскок. Обнаружилось по тому, что две модели дали побитово одинаковый Cd там, где обязаны были разойтись. Вместо булева «третий порядок» в Dyn теперь режим стенки числом, и все четыре модели живут на обоих бэкендах. clap отвергал --body-angle -8 как неизвестный ключ, из-за чего не запускался прогон на зеркальную симметрию профиля. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9120affab5 |
Эталонные течения статей, машиночитаемая сводка, радиус влияния и прореживание кадра
--case: помимо канала появились три ПЕРИОДИЧЕСКИХ постановки из статей — вихрь Тейлора-Грина
(разд. VI), дважды периодический сдвиговый слой (разд. VII) и затухающая двумерная
турбулентность. Перенос уже был периодичен, поэтому достаточно не накладывать ГУ вовсе.
Стартовые поля строятся общим кодом cpu::initial_field для обоих бэкендов: GPU только
перекладывает результат в свою раскладку, так что эталон задан ровно одним куском кода.
Три вещи, без которых эталоны считались неверно, и все три нашлись замером:
* в периодической постановке всё равно строилось тело — цилиндр по умолчанию стоял прямо
посреди эталонного течения. Теперь сцена пуста;
* Re считался по калибру тела, а статьи определяют его по РАЗМЕРУ ДОМЕНА (Re = u0*N/nu);
* и, главное, включалась выходная губка, поднимающая вязкость в 30 раз на последних столбцах.
Отсюда затухание было в 1.37 раза выше положенного. Губок в периодической постановке нет.
Что эталоны показали. Сдвиговый слой при Re=3e4 воспроизводит главное утверждение статьи:
KBC доживает до конца (энстрофия 0.871 от начальной), LBGK разваливается на шаге 4480.
Тейлор-Грин на CPU даёт порядок сходимости 2.03 и 2.01 — чистый второй.
ЗАМЕРЕНА ГРАНИЦА ПРИМЕНИМОСТИ f32, ровно та, ради которой планировалась отдельная группа
прогонов. Ошибка против точного решения, диффузионное измельчение:
N=64 CPU 9.83e-3 GPU 9.80e-3
N=128 CPU 2.40e-3 GPU 3.29e-3
N=256 CPU 5.97e-4 GPU 2.37e-2
GPU совпадает с f64, пока истинная ошибка выше ~1e-3, и промахивается в 40 раз, как только
она опускается ниже. Правило для кампании: точностные исследования сходимости — только на CPU.
--summary: сводка прогона одним JSON (St, Cd, rms Cl, Cm по каждому телу, ⟨ρ⟩, пульсация,
статистика гамма, радиус влияния, MLUPS, признак развала). Без неё разбор кампании из десятков
прогонов пришлось бы вести глазами.
--gif-downsample: усреднение блока k*k в пиксель. Без него кадр с сетки 4096x2048 неподъёмен;
проверено на 960x480 при k=3 — гифка 320x160 и 0.17 МБ.
РАДИУС ВЛИЯНИЯ в отчёте и сводке: докуда тело возмущает поток больше чем на 1% от U, в
калибрах, с предупреждением, если возмущение достаёт до границы домена. На проверочном прогоне
боковое влияние вышло ровно на границу (5.0 калибра при полуширине 5.0) — сигнал работает.
Также: --case-csv с рядом энергии, энстрофии и палинстрофии; --series-every для прореживания
рядов на сверхдлинных прогонах.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
b97686c8db |
Несколько тел в домене, произвольная геометрия и разбор стартовой акустики
НЕСКОЛЬКО ТЕЛ (--bodies). SDF сцены — минимум по телам, поэтому маски, поле SDF и Bouzidi-линки строятся ровно теми же процедурами, что и для одного тела. Каждому линку проставляется индекс тела, и сила считается по телам отдельно: для тандема и многоэлементных конфигураций литература даёт коэффициенты каждого тела, общей суммы недостаточно. Разнос идёт по четырём вёдрам; тела за пределом набора сваливаются в последнее, так что сумма по вёдрам всегда точна. Синтаксис: "cylinder:d=24,x=120,y=120; cylinder:d=24,x=192,y=120". Проверка на тандеме L/D=3 при Re=150 даёт физически правильную картину: передний цилиндр Cd=1.22 при rms Cl=0.02, задний Cd=-0.10 при rms Cl=0.19. Отрицательное сопротивление заднего — классический режим экранирования, когда сдвиговые слои переднего замыкаются на задний, а восьмикратный рост rms Cl отвечает тому, что задний треплет след переднего. ПРОИЗВОЛЬНЫЙ МНОГОУГОЛЬНИК (--shape polygon --poly) — точный SDF уже был, добавлен разбор. Открывает клинья, зазубренные кромки, любые обводы. Проверено на клине: 100% линков идут по интерполяционной формуле. ПРОФИЛИ С ИЗГИБОМ (--naca 4412). Средняя линия по четырёхзначной серии, поверхности откладываются по НОРМАЛИ к ней, а не по вертикали — иначе у заметно изогнутого профиля толщина у носка завышается. При нулевой кривизне вырождается в прежний симметричный 00xx. ПРОРЕЖИВАНИЕ РЯДОВ (--series-every): на 5·10^6 шагов полный ряд занимал бы сотни мегабайт. РАЗБОР СТАРТОВОЙ АКУСТИКИ. Добавлена x-t диагностика (--xt): срез ⟨ρ⟩ и u_x вдоль осевой линии, по строке на срез. Что она показала: * возмущение рождается НА ТЕЛЕ, а не на входе. На нулевом шаге |ρ−1| ≈ 1.3·10^-2 у задней кромки при 10^-8 у входа и выхода; причина — поле стартует однородным потоком сквозь то место, где стоит тело; * дальше импульс уходит полосой через весь домен — ровно то, что наблюдалось на низких Re; * на Re ≥ 1000 при выключенной губке он раскачивает неустойчивость у ВЫХОДА, и счёт гибнет около шага 17000. С губкой (умолчание) Re=2000 доживает. Попытка лечения сглаживанием стартовой скорости у тела (--init-taper) ЗАМЕРЕНА И ОТВЕРГНУТА: ширина 24 клетки давит возмущение нулевого шага восьмикратно (2.54·10^-2 → 3.13·10^-3), но пик за прогон при этом даже растёт (2.54·10^-2 → 3.22·10^-2). Возмущение просто переносится во времени. Ключ оставлен выключенным, замер записан в его описании. Добавлена губка у входа (--sponge-in); профиль β вынесен в общую math::beta_profile, раньше он дублировался в обоих бэкендах. Исправлен вердикт о пульсации: при развале счёта отношение уходило в минус и проверка «> 0.05» молча не срабатывала. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
039e5f4806 |
Батчинг GPU-чтения, компенсированные суммы и HRR-стенка на обоих бэкендах
БАТЧИНГ. Раньше после каждого шага делался map_async + poll(Wait) ради 48 байт итогов: на сетке 240×120 счёт упирался в 863 шаг/с при том, что сам счёт занимал 0.27 мс из 1.16 — три четверти времени машина стояла. Теперь k_stats2 пишет итоги в слот истории step % 128, а хост читает пачкой. Контракт бэкенда изменён с пошагового step() на advance(&mut out) + flush(): записи дописываются в вектор, синхронизация происходит только там, где дальше нужны актуальные данные (кадр гифки, живая строка отчёта). Замер: 240×120 — 6715 шаг/с против 863 (7.8×); пропускная способность 195 MLUPS против прежних 105. Паритет CPU/GPU сохранён: Cd 1.798 против 1.799, ⟨ρ⟩ 1.00090 против 1.00091. КОМПЕНСИРОВАННОЕ СУММИРОВАНИЕ (Кэхена–Ноймайера) в редукциях статистики и в сумме сил. Именно там теряется основная точность f32: наивная сумма по 10^5…10^7 узлам съедает ~log2(N) бит. Аппаратного f64 на целевом железе нет (в WGSL типа f64 не существует вовсе, а локальная Iris Xe сообщает shaderFloat64 = false), поэтому компенсация — единственный доступный способ. РАЗВАЛ СЧЁТА теперь ловится по уже посчитанным max|u| и ⟨ρ⟩, а не полным проходом по полю: на сетке 4096×2048 такой проход тянет с устройства сотни мегабайт, и делать его регулярно нельзя. Полная проверка осталась одна, в конце. HRR-СТЕНКА (--wall hrr, теперь умолчание). Условие Града — это ряд Эрмита, оборванный на 2-м порядке (ρ, u, Π). HRR продолжает его на третий, вычисляя коэффициенты не из популяций (их на стенке как раз и не хватает), а рекурсивно из уже известных: a₃_xxy = 2·u_x·a₂_xy + u_y·a₂_xx, a₃_xyy = 2·u_y·a₂_xy + u_x·a₂_yy В D2Q9 a₃_xxx и a₃_yyy решёткой не поддерживаются и отбрасываются. Третий порядок не трогает ρ, ρu и Π — соответствующие моменты весов обнуляются по симметрии, — что закреплено тестом. Моментная стенка перенесена на GPU (раньше её там не было вовсе, бэкенд отказывался запускаться): буфер индекса граничных узлов плюс ядро k_moment_wall. Скорость на момент t берётся из пост-столкновительного поля — столкновение сохраняет ρ и ρu, поэтому отдельное хранилище прошлого шага не нужно ни на одном бэкенде. Добавлена проверка лимита storage-биндингов адаптера: моментной стенке нужно 9 против 8 гарантируемых. Паритет на HRR: Cd 1.847 (CPU) против 1.848 (GPU). Три модели стенки на одной постановке дают 1.847 (hrr) / 1.845 (grad) / 1.856 (bouzidi). 33 теста, обе сборки чисты. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e3c2d417f8 |
Длительность в секундах (--time) и граничное условие Града как альтернатива Bouzidi
--time <секунды> задаёт длительность прогона прямо в физическом времени, число шагов считается как time/δt. Мотив: шаг не есть фиксированная порция времени — δt = u_lat·δx/u_phys привязан к размеру клетки, поэтому одно и то же число шагов на разных сетках покрывает разное физическое время (клетка втрое мельче ⇒ вместо 6 секунд получается 2). ГРАНИЧНОЕ УСЛОВИЕ ГРАДА (--wall grad) по Dorschner, Bösch, Chikatamarla, Boulouchos, Karlin, J. Fluid Mech. 801 (2016), разд. 2.1 и прил. B: недостающие популяции задаются не напрямую, а через целевые моменты — скорость (B 1), плотность (B 3) и тензор давлений (2.14)–(2.16), — после чего собираются приближением Града (2.13). Переиспользует grad_init, уже проверенный на эталоне Тейлора–Грина. Скорость на момент t берётся из пост-столкновительного поля: столкновение сохраняет ρ и ρu, поэтому отдельное хранилище прошлого шага не нужно. Добавлена также заведомо ступенчатая модель (--wall staircase) — не для счёта, а как база сравнения, показывающая, сколько именно даёт субсеточность. ИЗМЕРЕНО, насколько каждая модель субсеточна. Тело сдвигается внутри клетки, смотрится разброс Cd (Re=20, D=16, стационар): staircase 1.11%, grad 0.64%, bouzidi 0.19%. Град оказывается ровно между ступенькой и Bouzidi, и это следует из его устройства: положение стенки входит туда только через целевую скорость — одну усреднённую по узлу величину, тогда как Bouzidi подставляет свою долю пересечения в каждую популяцию отдельно. Сходимость по разрешению тела (домен 15D×10D, Re=20): bouzidi 2.581/2.529/2.521 и grad 2.618/2.534/2.521 при D=8/16/32. Обе состоятельны, сходятся к одному пределу с наблюдаемым порядком ≈2.7 и к D=32 неразличимы; на грубой сетке Град заметно хуже. Ступенчатая модель при D=16 даёт 2.69, то есть +6.7% к пределу против +0.4% у субсеточных. Заявленного в статье выигрыша Града по устойчивости на высоких Re в здешней канальной постановке воспроизвести не удалось: обе модели теряют счёт на одном и том же Re, то есть ограничивает не стенка. Поэтому умолчание остаётся bouzidi. В шапку добавлена диагностика границы тела: сколько линков идут по интерполяционной формуле, сколько сваливаются в ступенчатый отскок, каков разброс доли пересечения. На NACA и цилиндре интерполяция покрывает 100% линков. GPU-бэкенд условие Града пока не поддерживает и при таком выборе отказывается запускаться явно, а не считает молча по Bouzidi. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f174f1c9a0 |
Сверка со статьями: эталон Тейлора–Грина, выбор модели KBC, лечение продольной акустики
Проверка переориентирована на первоисточники из docs/origins вместо сравнения с
python-прототипом.
ЭТАЛОН ТЕЙЛОРА–ГРИНА (разд. VI 2D-статьи) — единственное течение статьи с точным
аналитическим решением. Второй порядок сходимости воспроизведён: 2.05 и 2.01 на
сетках 64/128/256, отдельно по амплитуде и по форме. Два места потребовали разбора:
* начальное давление. Течение несёт собственное поле p ~ ρu₀², находимое из
∇²p = 2ρ(ψ_xx·ψ_yy − ψ_xy²). Старт с ρ ≡ 1 сбрасывает разницу в акустику, которая
в периодическом ящике не затухает и садится полкой на ошибку. Работа 2024 года
делает то же самое явно, решая ∂ρ/∂t + ∇·(ρu₀) = D∇²ρ до стационара;
* способ измельчения. При фиксированном u₀ ошибка упирается в полку O(Ma²) (измерено:
относительная ошибка формы ∝ u₀^1.07). Порядок виден целиком только при диффузионном
измельчении, ν = const и u₀ ∝ 1/N. Это свойство слабо-сжимаемого метода, а не
реализации: LBGK на том же тесте даёт ту же полку, что согласуется с утверждением
статьи о практически одинаковом поведении всех моделей.
Добавлено приближение Града (ур. 58) для согласованного старта эталонов.
СВЕРКА ОПЕРАТОРА с пошаговым листингом работы 2024 года — совпадает дословно, включая
Δh = f − f^eq − Δs и f′ = f − β(2Δs + γΔh). Та же работа подтверждает относительный
порог вырожденности γ: стабилизатор «далеко не постоянен», а MRT с γ = const не
достигает той же устойчивости.
ВЫБОР СОСТАВА СДВИГОВОЙ ЧАСТИ (табл. I) — ключ --kbc-model: n1 = {N, Π_xy} (KBC D),
n2 = {N, Π_xy, T} (KBC C). По точности неразличимы, как и заявляет статья. Разница в
объёмной вязкости (ур. 57): у n2 она фиксирована ξ = ν, у n1 равна c_s²(1/(γβ) − ½) и
при измеренной ⟨γ⟩ ≈ 1.73 < 2 в среднем вчетверо больше ν. Поэтому вопреки ожиданию
именно n1 сильнее демпфирует продольную акустику и оставлен умолчанием.
ПРОДОЛЬНАЯ АКУСТИКА КАНАЛА — найдена причина «поршневого» поведения потока. Вход по
скорости акустически есть жёсткий поршень, выход по давлению — открытый конец; канал
работает четвертьволновым резонатором. Измерено: период пульсации 3332 шага против
расчётных 4·Nx/c_s = 3325, первый ноль автокорреляции ровно на Nx/c_s, затухание за
30000 шагов — 3.8%, то есть мода не гаснет. Возбуждал её сам старт из покоя.
Исправлено умолчаниями: --init uniform (домен сразу заполнен потоком) и автоподбор
губки по длине домена. Пульсация упала с 75% до 1.0% от скорости потока, а числа
выправились сами: St 0.183 против прежних 0.194 (литература 0.183), rms Cl 0.38
против 0.84, ⟨ρ⟩ = 1.0000. Отчёт теперь печатает период моды, время её вязкого
затухания и измеренную пульсацию с предупреждением при превышении 5%.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
5d762ad9d6 |
Решатель KBC-2D на Rust: ядро по статьям, два бэкенда, гифка в реальном времени
Переписанный с нуля двумерный решатель LBM D2Q9 с энтропийным столкновением KBC в варианте «модель D» (табл. I 2D-статьи Bösch/Chikatamarla/Karlin, arXiv:1507.02509; в трёхмерных работах — KBC-N1). Код разложен по ролям на пять файлов: математика решателя, бэкенд под процессор, бэкенд под видеокарту, оркестратор, блок гифок. Ядро сверено с первоисточниками тестами (22 шт.): * проектор на сдвиг, выписанный аналитически из представления популяций через натуральные моменты (ур. 10), совпадает с матричным до 1e-13, идемпотентен; * γ из замкнутой оценки (ур. 17) — корень условия максимума энтропии (ур. 15); * сдвиговые моменты релаксируют ровно с 2β при любой γ, вязкость по ур. (5) воспроизводится затуханием сдвиговой волны с точностью лучше 1%; * сквозной бенчмарк статьи (дважды периодический сдвиговый слой, Re=3e4) сходится с fp64-эталоном питоновского решателя 0.6035. Порог вырожденности γ относительный (доля от ⟨Δ|Δ⟩): абсолютный подменял бы γ на 2 на большинстве узлов, молча превращая KBC в LBGK. Доля таких узлов печатается в отчёте. Бэкенды взаимозаменяемы и согласованы: CPU (rayon, f64) и GPU (wgpu/WGSL, f32) на одной постановке совпадают до 4–5 значащих цифр шаг в шаг; на Intel Iris Xe GPU даёт ~105 MLUPS против ~18 у процессора. Топология задачи строится один раз в cpu.rs и загружается в буферы, дублируется только физика — в WGSL. Анимация привязана к физическому времени потока, а не к скорости счёта: задержка кадра берётся из δt = u_lat·δx/u_phys. Дробная задержка раскладывается по целым сотым долям секунды накопителем (3,3,4,3,3,4,…), поэтому накопленное время кадров не уходит от физического; режим --gif-every auto подбирает шаг под реальное время при заданной частоте. Параметризовано: скорость и направление потока, число Рейнольдса, размер домена и размер ячейки в метрах, коэффициент и границы вложенного патча измельчения, семь форм тела (цилиндр, квадрат, ромб, эллипс, профиль NACA, треугольник, пластина) с углом атаки, время и разгон, оператор столкновения, режим выхода и губка, бэкенд, вся анимация и три уровня подробности отчёта. Известное расхождение с питоновским решателем на канальном случае (Cd выше на 9%, St ниже на 12% при совпадающих ⟨ρ⟩, ⟨Cm⟩ и rms Cl) описано в README вместе с тем, что уже исключено как причина. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |