А2: сходимость в штиль

01.10.2026. Ветка air/a2: feature/air-model 0788cf7 плюс влитая разведка air/a2-pre. Разведка, наборы old/new и матрица описаны в docs/archive/plan/air-model-a2pre.md. Код и данные лежат в tools/research/a2pre/, команды — в README, раздел «А2». Все таблицы строит один скрипт: analyze_a2.py → out/a2_tables.md.

Итог

  • k_relax 0,5 → 0,1. Это нижняя релаксация обновления местного K(Ri). Значение сменено в air.py → Params и AirCase.p.
    • Сходятся все cbl-случаи обоих наборов: old 16/16, new 16/16.
    • Цепочка игры стоит столько же итераций: old 892 → 892, new 3712 (с предельным циклом) → 922.
    • Физика та же: сошедшиеся cbl-решения отличаются от прежних не больше чем на 0,01 м/с по ключевым числам.
  • Пропуск прохода θ′_d у решения без нагрева (air.py и GPU). Без нагрева и без θ′_d у родителя уравнение θ′_d однородно, его решение θ′_d ≡ 0 точно. Итерации те же, итерация стоит 79 % от полной на 400 м (3,18 против 4,02 мс) и 76 % на 200 м. Так в игре дешевле каждое решение пары «без нагрева», то есть w_mech.
  • Параметры модели игры не менялись: λ/h, α, z0, max_profile, wind.*. Схема, условие на выходной грани и потолок окна (C1 v3, C7 v3) тоже не трогались.

Что гоняли

Условия — как в разведке. Онгудай, 12:00, ветер 150°. Цепочка «область 400 → окно 100 → окно 50 м» и отдельно область 200 м. Случаи: штиль и 3 м/с, с нагревом и без; heat_mode cbl и surface. Критерий: mom 2e-5, θ′ 5e-7, div 1e-6, не больше 3000 итераций. Эталон — air.py, float32, CuPy, RTX 4070 SUPER. GPU делили с тестами и чужим процессом, поэтому время шумное, а итерации детерминированы.

  1. Первая пачка — варианты из задания (run.py a2, 84 сценария на old и new):

    • kr25 — k_relax 0,25;
    • hs8 — heat_sweeps 8;
    • kr25hs8 — их сочетание;
    • top3000 — диагностика гипотезы разведки: потолок окон 3000 м над рельефом окна вместо 2000 м. Тогда z_i лежит ниже верхней зоны релаксации к родителю.
  2. Отбор (run.py scan). Проверяли ещё дешёвые численные параметры в границах C1 на двух трудных cbl-случаях. Первый — new, штиль, нагрев, цепочка 400/100/50; не сходится окно 50 м. Второй — old, область 200 м, 3 м/с, нагрев; это случай А1.3. Результаты по вариантам:

    вариантnew: окно 50 м в штильold: 200 м, 3 м/с
    k_relax 0,1ok 211ok 171
    dtau_th 600maxmax
    dtau_th 300maxmax
    dtau_per_m 0,2maxmax
    dtau_per_m 0,15maxmax
    mom_sweeps 4maxmax
    vcycles 2maxmax

    Оба случая сводит только k_relax 0,1.

  3. Вторая пачка (run.py a2 kr10,kr15, 48 сценариев) — k_relax 0,1 и 0,15 на полной матрице.

Результаты

Цепочка игры — cbl, 400/100/50, штиль и 3 м/с, с нагревом и без: 12 решений. Её цена — Σ итераций × мс/итер варианта (CuPy). У heat_sweeps 8 и высокого потолка окна итерация дороже, у k_relax цена итерации та же.

вариантнаборcbl не сошлосьsurface не сошлосьΣ итер. цепочкимс/итер 400 / 100 / 50 мцена цепочки, с
до А2 (k_relax 0,5)old1 / 16 (200 м, 3 м/с)1 / 889211,8 / 9,5 / 14,811,0
до А2new1 / 16 (окно 50, штиль)5 / 8371213,0 / 10,5 / 15,455,5
kr25old0 / 161 / 889212,1 / 9,9 / 15,111,3
kr25new1 / 162 / 8371212,3 / 9,9 / 15,354,5
hs8old0 / 161 / 897216,5 / 13,1 / 19,416,2
hs8new1 / 164 / 8380216,6 / 13,1 / 19,571,5
kr25 + hs8old0 / 161 / 895216,7 / 13,2 / 19,716,1
kr25 + hs8new0 / 162 / 8104222,2 / 13,4 / 25,522,9
top3000 (диагностика)old1 / 121 / 6372212,3 / 12,1 / 20,069,9
top3000new1 / 123 / 6372212,3 / 12,0 / 20,069,7
kr10 (выбран)old0 / 160 / 889212,2 / 10,1 / 15,411,5
kr10new0 / 162 / 892212,2 / 10,0 / 15,412,0
kr15old0 / 160 / 888213,6 / 11,0 / 17,212,6
kr15new0 / 162 / 893213,0 / 11,0 / 16,413,0

Итерации по решениям. Для цепочки — 400/100/50, для области — 200 м; max — предел 3000:

условияold доold kr10new доnew kr10
штиль, нагрев, cbl161/71/161161/61/161161/61/max161/61/211
3 м/с, нагрев, cbl131/41/61131/41/61131/41/51131/41/51
3 м/с, без нагрева151/41/41151/41/51141/41/51141/41/51
штиль, без нагрева11/11/1111/11/1111/11/1111/11/11
200 м, штиль, нагрев, cbl261251251261
200 м, 3 м/с, нагрев, cblmax171181181
200 м, 3 м/с, без нагрева191191201201
штиль, нагрев, surface271/181/max261/191/201max/max/maxmax/2041/471
3 м/с, нагрев, surface121/51/91121/51/81131/61/max131/61/61
200 м, штиль, surface481471maxmax
200 м, 3 м/с, surface541161181181

Выбор: k_relax 0,1.

  • Среди вариантов, где сошлись все cbl-случаи обоих наборов (kr10, kr15, kr25 + hs8), у него наименьшая цена цепочки: 11,5 + 12,0 с.
  • По итерациям kr10 и kr15 равны: 892 + 922 и 882 + 932. kr10 взят из-за запаса. Граница лежит между 0,15 и 0,25: при 0,25 окно 50 м в штиль снова в цикле, при 0,15 оно сходится за 221 итерацию, при 0,1 — за 211. Волна Б сменит час, место и параметры, и больший запас демпфирования там нужнее, чем 0,01 м/с расхождения.
  • Сочетание kr25 + hs8 тоже сводит все cbl-случаи, но на 13 % больше итераций (1042 против 922 на new). К тому же итерация на 30–80 % дороже из-за 8 прогонок тепла. Цена цепочки — 22,9 с против 12,0 с на new.

Почему это работает.

  • Предельный цикл — это обратная связь «местное K(Ri) ↔ θ′ ↔ w» у верха слоя перемешивания. Число Ri, а с ним и K, отзывается на dθ′/dz этой же итерации.
  • При малом λ/h фоновое K мало, и K(Ri) скачет от итерации к итерации. Нижняя релаксация 0,1 сглаживает K по итерациям Пикара: обновление за итерацию не больше 10 % разрыва до K(Ri).
  • Неподвижная точка при этом та же: K = K(Ri), уравнения не меняются.
  • Лишние прогонки тепла (hs8) не помогают: цикл сидит не в неточности решения θ′ за итерацию, а в связи с K. Меньший шаг псевдовремени (dtau) и больше прогонок импульса тоже не помогают.

Гипотеза «потолок окна» отвергнута. top3000 ставит z_i ниже верхней зоны релаксации окна, но окно 50 м в штиль не сходится на обоих наборах. На old оно даже сломалось: max против 161 итерации до. Правка C7 не нужна.

Физика не изменилась

  • Сошедшиеся решения kr10 сравнивались с сошедшимися решениями до А2 по ключевым числам: подъём у старта, скорость и θ′ на 50 м, max w, p99 w на 200 м. Наибольшее |Δ| на cbl — 0,007 м/с для old (15 пар решений) и 0,009 м/с для new (15 пар), оба раза в области 200 м в штиль.
  • Для сравнения: холодный и тёплый старт одной задачи до А2 расходятся на 0,062 м/с max|Δu|.
  • На surface наибольшее расхождение 0,06 м/с (max w, окно 100 м в штиль, old) — в пределах того же допуска остановки.

Подъём у старта (окно 50 м), м/с:

условияold доold kr10new доnew kr10
штиль, нагрев, cbl0,4070,4050,504 (max)0,507
3 м/с, нагрев, cbl1,331,331,451,45
3 м/с, без нагрева0,8940,8920,980,98
200 м, 3 м/с, нагрев, cbl1,44 (max)1,431,561,56

Askervein check25 (recal/run_grid.py check25, нейтральный случай): χ² 68,06/44 — как до А2. Итераций 91, как до. Наблюдаемые отличаются не больше чем на 1e-5.

Игра: до и после (GPU-решатель, параметры игры = old)

Логи — tools/research/a2pre/out/bench/{before,after}_*.log. Итерации те же. Время шумное: те же прогоны до А2 давали разброс 4,2–4,6 мс/итер на 400 м.

чтодо А2после
GPU-тесты test_air_58/5858/58
контракты (headless)—26/26
bench 400 м: итерация / без θ′_d4,64 мс / —4,02 мс / 3,18 мс (79 %)
bench 200 м: итерация / без θ′_d16,3 мс / —15,0 мс / 11,3 мс (76 %)
bench 400 м, пара (без + с), загрузка280 итер. (151 + 131), GPU 1,44 с280 итер., GPU 1,15 с
bench 200 м, 3 м/с с нагревомпропущено (max 3000)ok 170 итер. (эталон 171), GPU 2,7 с
bench 200 м, 3 м/с, пара, загрузкапропущено370 итер. (191 + 171), GPU 5,2 с
«Рассчитываем ветер», область 400 м, 3 м/с (AirRuntime)3,73 с стены, GPU 1,37 с, итер. [150, 130]3,55 с, GPU 1,17 с, [150, 130]
загрузка с окнами (AirRuntime)6,49 с стены6,29 с
окно 50 м при загрузкеитер. [40, 70], GPU 635 мс[50, 60], GPU 567 мс
пересчёт по сроку, тёплыйGPU 1,02 с, итер. [80, 130]GPU 0,91 с, [80, 130]

На новых параметрах (волна Б) цепочка в штиль сходится за 161/61/211 итераций вместо предела 3000 в окне 50 м.

Тёплый старт того же поля — теперь 20 итераций вместо 10.

  • K не входит в состояние тёплого старта. Он начинает с K_b и при k_relax 0,1 доходит до K(Ri) за ~20 итераций (0,9²⁰ ≈ 0,12).
  • Порог проверки в test_air_picard_bench.gd::test_warm_start поднят с 10 до 20.
  • Тёплый пересчёт соседнего срока (12:00 → 12:30) — 50 итераций, как до.
  • Расхождение холодного и тёплого решения 12:30 выросло до 0,113 м/с max|Δu| (было 0,062). Оба решения прошли критерий, это его допуск при более медленном K.

Фикстуры

Пересчитаны тем же генератором, k_relax теперь записан в params JSON:

  • picard/: picard_gpu_refs.py. Область 200 м, 3 м/с с нагревом: max 3001 → ok 171.
  • window/: window_gpu_refs.py. Окно 50 м без нагрева: 41 → 51.
  • ref/: fixtures.py, итерации те же — 10/710/60/320.

Пропуск 200 м, 3 м/с в bench снят.

Границы

  • Одно место и один час: Онгудай, 12:00, 150°. Ветер 0 и 3 м/с, 6–8 м/с в матрице не считались (6 м/с есть в фикстурах и bench — сходится).
  • Surface (выбор heat_mode — задача А3) на new не сходится в 2 из 8 решений: штиль, область 400 и 200 м — длинные волны невязки, как в разведке. k_relax их не лечит (при 0,25 — тоже 2 из 8).
  • Точки Морриса не пересчитывались (волна Б).
  • Цена итерации в CuPy и в Godot разная (400 м: 12 и 4 мс), отношение вариантов то же.

Открытые вопросы (координатору)

  • K в состоянии тёплого старта (state(): nu, nuh) вернуло бы тёплому старту тех же условий 10 итераций вместо 20 и сократило бы пересчёт в полёте на время релаксации K. Это меняет формат state() и warm (C9, AM-06Б) — через координатора.
  • Правки C1 v3 (θ′ на выходной грани) и C7 v3 (потолок окна) по итогам А2 не нужны.