Контракты модуля ann2

План — docs/plan/ann2.md (§3 — архитектура), журнал — docs/plan/ann2/. Код — исследовательский, tools/research/ann2/; данные и прогоны — $AIR_NN_DATA/ann2/ (AIR_NN_DATA по умолчанию /home/greg/air_nn_data). Изменение интерфейса — только через координатора: версия +1, что изменилось, уведомление потребителей.

A1. Данные и деление (версия 2)

Владелец: air-nn (П1 v3, П2 v4 «деление v3», П6), в ann2 — только чтение. Потребители: AN-2, AN-3.

  • Образцы — П1 v3 (docs/contracts/air-nn.md): наборы из tools/research/air_nn_pilot/config.yaml (main v1 + terrain v2/v3), cases/<id>.npz, оси [канал, высота, j, i], 13 высот AGL, dx = 400 м, 96 × 96; цель несошедшихся — target П1 v3.

  • Деление — то же, что у P2 (П2, «деление v3»): (г) отложенные горные системы — главная оценка, (б) Онгудай, (б′) отложенные процедурные, (а) новые условия, обучение. Тот же конфиг и зерно, что у прогона P2 runs/2026-10-03_p2b, — чтобы сравнение шло на тех же случаях.

  • Чтение, повороты, карты П2 v4 и деление — импортом из tools/research/air_nn_pilot/ (dataset.py, prep и т. п.), без копирования и без правки этого каталога. Не хватает функции — обёртка в tools/research/ann2/.

  • v2 (04.10, AN-4; решение пользователя — план §5 п. 6): режим случая — механический / конвективный по w*/U (w* по Дирдорфу из потока тепла и z_i случая, U — скорость притока, на какой высоте — записать) или z_i/L; порог и обоснование (литература + где решатель перестаёт сходиться) — в README tools/research/ann2/, одна функция regime(case). Обучение и проверка ann2 — только механические случаи; деление по местам и зерно — как v1 (конвективные случаи просто выпадают, отложенные группы не меняются).

A2. Вход и выход сети ann2 (версия 2)

Владелец: AN-2. Потребители: AN-3 (обучение, оценка), будущий экспорт ONNX.

  • Система координат: повёрнутая, как П2 v4 — точный поворот на k·90° (ветер «с запада» ±45°), остаток угла r — в условия (cos r, sin r); отражение поперёк ветра (y′ → −y′) — аугментация обучения по правилам П2 v4. Обратный поворот и отражение выхода — точные (тест: поворот ×4 и R∘R = тождество).

  • Карты 2D (float32, C_in × H × W): рельеф h − локальное среднее и прочие карты — из того, что есть у игры без решателя (рельеф, вода, поток тепла, z0); список, формулы и фиксированные нормировки — в README tools/research/ann2/. Абсолютных координат клетки нет (сеть полностью свёрточная, работает на карте любого размера); признак, связанный с краем области решателя, — только с записью причины в README.

  • Профиль атмосферы: столб притока U(z), θ(z) на сетке высот (не меньше 13 уровней П1) — вход 1D энкодера; плюс скаляры (cos r, sin r, dx, при необходимости z_i, Fr) — список и нормировки в README. Из профиля — FiLM 2D энкодера.

  • Высота: непрерывная η (м над рельефом клетки); в обучении — случайная в [25, 2000] м, цель — линейно между соседними уровнями П1; в оценке — заданные высоты.

  • Выход колонки на высоте η (порядок каналов фиксирован): du_par, du_perp, w — в долях max(U(η), 1 м/с) (U(η) — скорость притока на этой высоте; du — отклонение от профиля притока), theta — θ′ в К (или на записанный масштаб), logsig_* — log σ по каждому из четырёх каналов. Решение без нагрева (m) — образец с нулевой картой потока тепла и маской на θ′ (если метаданные это подтверждают; иначе — записать, как сделано). Обратное преобразование в м/с, К и исходную систему — одна функция, ею пользуются и обучение, и оценка.

  • Потеря: NLL Гаусса по каналам с масками + малый штраф дивергенции массового потока (вес — в конфиге).

  • Прогон: $AIR_NN_DATA/ann2/runs/<имя>/: config.json (всё для воспроизведения, зерно), model.pt (state_dict + config), train_log.csv (шаг, потеря обучения, потеря проверки, время), metrics.json (A3). Продолжение после прерывания — с последней точки сохранения.

  • Контрактный тест: tools/research/ann2/tests/test_contract.py — формы и dtype батча загрузчика и выхода сети, карта 64² и 96² (полная свёрточность), поворот ×4 и R∘R = тождество, обратное преобразование выхода, конечность.

  • v2 (04.10, AN-4): условия — N и Fr⁻¹ случая (из Day.gamma и профиля случая, а не по фону 3 К/км), остальной состав и порядок скаляров — как v1, изменения нормировок — в README. Голова колонки — разложенная (как DeepONet): ветвь по η даёт K ≈ 16 базисных профилей на каждый канал (один раз на высоту, общие для всех клеток), карта признаков — K коэффициентов на клетку и канал, выход — скалярное произведение; каналы выхода, единицы и обратное преобразование — как v1. В каталоге прогона + best.pt — точка с наименьшей потерей проверки (ранняя остановка), её и оценивают.

A3. Оценка против P2 (версия 2)

Владелец: AN-2. Потребители: AN-3, координатор, пользователь.

  • Скрипт tools/research/ann2/evaluate.py --run <каталог прогона> считает на тех же случаях и клетках сеть ann2 и P2 (runs/2026-10-03_p2b) и печатает таблицу; итог — metrics.json и eval.md в каталоге прогона.
  • Точки: все клетки области без edge_cells на 60 м над рельефом (линейно между 50 и 75 м), как П3 v2; полная карта 96² (сеть ann2 — целиком, без вырезок).
  • Числа (решение 04.10 — критерий 10–15 % скорости): ошибка ветра |Δ(u, v)| с нагревом — медиана и p90 по клеткам группы (как отчёт P2), м/с; относительная |Δ|/|V_решателя| — медиана; ρ₁₅ = |Δ| / max(0,15·|V_решателя|, 0,01 м/с) — медиана и p90; доля «ок15» (ρ₁₅ ≤ 1) и «ок10» (|Δ| ≤ max(0,10·|V|, 0,01 м/с)); 0,01 м/с — порядок шума решателя (AN-1). Подъём |Δw| с нагревом и без — медианы.
  • Группы: (г) все / сошедшиеся / несошедшиеся (по статусу решения h), (г) по корзинам U10 (0–2, 2–4, 4–6, ≥ 6 м/с), (б), (а), обучающие. Строка P2 в каждой группе; самопроверка — медиана ошибки P2 на (г) совпадает с отчётом P2 (≈ 0,62 м/с) до 0,01 м/с.
  • v2 (04.10, AN-4): evaluate.py --run <имя> [--also <прогон> …] — ann2 (точка best.pt, если есть), прочие прогоны ann2 и P2 на одних клетках. Группы v1 считаются по механическим случаям (A1 v2) + отдельная строка «конвективные» (все отложенные (г) конвективные случаи) для всех сетей. Обязательная строка criterion_10_15: выполнен|нет — медиана относительной ошибки на (г) механических ≤ 0,15 (допустимо) / ≤ 0,10 (цель), отдельно от сравнения с P2. Самопроверка P2 — как v1 (на прежней группе (г) всех случаев).