13 Commits
Author SHA1 Message Date
NotBigGhostandClaude Opus 5 d5e37fcb8d Раздельные привязки популяций: снят предел dzn на размер сетки
Массив популяций занимает nx*ny*9*4 байта и показывался шейдеру одной привязкой.
dzn объявляет max_storage_buffer_binding_size = 128 МиБ, поэтому потолок выходил
3.73 млн узлов: 14 прогонов кампании из 115 падали на создании bind group, а на
них приходится 70.7% её стоимости.

Ограничена при этом ровно привязка: max_buffer_size у dzn 2047 МиБ. Поэтому тот
же буфер теперь показывается девятью привязками, по одному направлению в каждой,
и потолок поднимается до 33.5 млн узлов — самая крупная сетка кампании
(4096x4096, 16.8 млн) проходит с запасом.

Как устроено:
 * шаг между направлениями выровнен на 256 байт (dir_stride), потому что смещение
   привязки обязано быть кратно min_storage_buffer_offset_alignment;
 * обращения к популяциям в WGSL идут через fget/fset/pget/pset, а их тело
   генерируется под вариант (build_shader);
 * вариант выбирается по max_storage_buffer_binding_size адаптера — где предела
   нет, собирается прежний общий, без switch в аксессорах;
 * KBC2D_SPLIT_POPULATIONS=1 включает раздельные принудительно: иначе сверить два
   варианта на одной карте нечем.

Проверено:
 * на одном драйвере оба варианта дают одно и то же — Cd 2.39486, energy_end
   5.74813e-05; раздельный стоит 5.7% пропускной способности;
 * на сетке 2048x2048, где раздельные привязки и нужны, нативный прогон против
   контейнерного: energy_end расходится на 2.4e-07, enstrophy_end на 1.3e-07;
 * preflight в контейнере: 115 сценариев из 115, ни одного отказа (было 14);
 * 29 собственных тестов решателя зелёные.

Образ опубликован как notbigghost/kbc2d:1.2.0 (он же latest).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 19:08:12 +03:00
NotBigGhostandClaude Opus 5 6344c7d205 Внятные сообщения preflight и предел dzn на размер привязки
preflight печатал последнюю строку вывода упавшего прогона, а у паники Rust
последняя строка — «note: run with RUST_BACKTRACE=1», то есть ноль сведений о
причине. Теперь из вывода достаётся собственное сообщение решателя, а для паники —
то, что стоит ПОСЛЕ строки «panicked at». Вместо

  A10_turb_n2048_kbc   note: run with `RUST_BACKTRACE=1` ...

печатается

  A10_turb_n2048_kbc
      wgpu error: Validation Error | In Device::create_bind_group, label = 'level'
      | Buffer binding 0 range 150994944 exceeds `max_*_buffer_binding_size` limit 134217728

Заодно задокументировано само ограничение. dzn объявляет
max_storage_buffer_binding_size = 128 МиБ против гигабайтов у нативных драйверов,
при том что max_buffer_size у него 2047 МиБ: держать большой буфер можно, показать
шейдеру одной привязкой — нет. Потолок выходит 3.73 млн узлов (около 1920x1920),
и 14 прогонов кампании из 115 в него не влезают. На эти 14 приходится 70.7%
стоимости, так что для кампании ограничение решающее.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 18:35:07 +03:00
NotBigGhostandClaude Opus 5 81985b169e Запуск в WSL2 через docker compose без NVIDIA-runtime
В WSL2 драйвера Vulkan для Linux у NVIDIA нет: карта отдаётся через /dev/dxg по
протоколу WDDM, нативный libGLX_nvidia про него не знает и перечисляет ноль
устройств, поэтому Container Toolkit нечего подкладывать внутрь и docker падает
с `could not select device driver "nvidia"`.

С /dev/dxg умеет говорить dzn (Dozen) — драйвер Mesa, транслирующий Vulkan в
D3D12. Он положен в образ, и NVIDIA-runtime для этого пути не нужен вовсе: нужны
проброс устройства и монтирование /usr/lib/wsl, где Microsoft держит libd3d12.so.

Правка в решателе одна: флаги инстанса wgpu теперь читаются из окружения
(InstanceFlags::from_build_config().with_env()). Без этого переменная
WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER не действует, а wgpu по умолчанию
МОЛЧА прячет адаптеры, не прошедшие тесты соответствия Vulkan, — под это правило
попадает dzn, и решатель сообщал, что GPU не найден. Поведение по умолчанию не
изменилось: без переменной такие адаптеры по-прежнему скрыты.

База образа сменена с debian:bookworm-slim на archlinux:base: в пакетах Mesa у
Debian и Ubuntu dzn не собирают (проверено по спискам файлов), в Arch он лежит
отдельным пакетом той же версии Mesa, что и на хосте WSL.

Новое:
 * docker-compose.wsl.yml — путь через /dev/dxg, с профилем проверок и с build:
   на случай, когда доступа к реестру нет;
 * bench/parity.py — сверка GPU-пути с CPU в f64 на течениях, где расхождение
   f32 и f64 не нарастает. Соответствие dzn вендором не проверено, значит
   проверяем сами, а не верим на слово.

Замерено на Intel Iris Xe (та же карта, нативный драйвер против dzn):
 * точность: cd 2.39486 против 2.39486, energy_end 5.74813e-05 против
   5.74810e-05 — совпадение до 5-6 значащих цифр;
 * скорость: плата за трансляцию падает с ростом сетки, 4.2x на 61 тыс. узлов,
   1.46x на 461 тыс., 1.30x на 1.84 млн. На 95.1% стоимости кампании сетки
   крупнее 600 тыс. узлов, поэтому ожидаемое удорожание — около трети, не в разы.

В калибровку добавлена сетка 1920x960: оценивать кампанию по 240x120 значит
занижать пропускную способность вчетверо. Образ опубликован как
notbigghost/kbc2d:1.1.0 (он же latest), проверен вытягиванием из реестра.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 17:15:53 +03:00
NotBigGhostandClaude Opus 5 e75b404f8a Опубликовать образ в реестр и добавить compose для сервера
Образ собран под linux/amd64 и выложен как notbigghost/kbc2d — теги 1.0.0,
latest и 8d17bc8 указывают на один digest sha256:932d881d. В Dockerfile
добавлены метки OCI: версия, хеш коммита и ссылка на репозиторий, чтобы
образ на сервере однозначно сопоставлялся с состоянием исходников.

docker-compose.server.yml самодостаточен: тянет готовый образ из реестра,
исходников не требует, на сервер переносится одним файлом. Кампания —
единственный сервис, поднимающийся по up -d; проверки (vulkaninfo,
preflight, calibrate, dry-run) вынесены в профиль check и сами не
стартуют. Порядок проверок задан документацией: карта, сценарии,
калибровка, смета — и только потом счёт.

Политика перезапуска on-failure, а не unless-stopped: кампания завершается
штатно с кодом 0, и «перезапускать всегда» крутило бы контейнер вхолостую
по кругу, тогда как падение и перезагрузку хоста on-failure подхватывает,
а --resume продолжает с места. Журнал ограничен по размеру: девяносто
часов вывода иначе съедят диск, полные логи каждого прогона всё равно
лежат в out/<id>/log.txt.

Проверено локально из каталога без исходников: образ тянется из реестра,
смоук проходит, результаты ложатся на хост. Часть с картой проверяема
только на сервере — здесь запрос устройства ожидаемо отвергается
«no adapters were found», что само по себе подтверждает, что резервация
GPU в compose действует.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 17:15:01 +03:00
NotBigGhostandClaude Opus 5 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>
2026-08-15 17:36:40 +03:00
NotBigGhostandClaude Opus 5 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>
2026-08-15 17:36:18 +03:00
NotBigGhostandClaude Opus 5 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>
2026-08-15 12:52:08 +03:00
NotBigGhostandClaude Opus 5 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>
2026-08-15 12:20:07 +03:00
NotBigGhostandClaude Opus 5 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>
2026-08-15 03:51:05 +03:00
NotBigGhostandClaude Opus 5 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>
2026-08-15 01:54:22 +03:00
NotBigGhostandClaude Opus 5 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>
2026-08-14 21:39:08 +03:00
NotBigGhostandClaude Opus 5 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>
2026-08-14 18:23:03 +03:00
NotBigGhostandClaude Opus 5 11ff7b79b4 Начальный коммит: Vulkan-редактор SimVulcan + исследование KBC-LBM
Состояние на момент заведения репозитория.

C++ приложение (src/, shaders/, tests/) — минимальный редактор 3D-моделей
на Vulkan 1.3: орбитальная камера, три опорные сетки через начало координат,
загрузка .obj с режимами отображения. Весь Vulkan изолирован в src/vk/.

Исследование (docs/) — оригинальные статьи по KBC (docs/origins) и
Python-решатель D2Q9 KBC-N1 с AMR 2x и SDF+Bouzidi (docs/theory).

В решателе перед коммитом исправлены дефекты, найденные сверкой с
первоисточниками: относительный порог знаменателя энтропийного стабилизатора
(абсолютный вырождал KBC в LBGK на 77-99% узлов), заворот вход/выход в углах
домена, диагностика средней плотности по фиктивным узлам тела, зашитый
refine=2. Подробности — docs/theory/solver_2x_sdf/README.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 16:37:38 +03:00