textmachine/eval/dovodka/power.py

197 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""МОЩНОСТЬ СУДЕЙСКИХ ОСЕЙ ФАЗЫ Д — считается ДО покупок. $0.
⚠ Зачем. Эксп-22 купил английскую ось на n=6 и лишь ПОСЛЕ денег обнаружил, что при шести единицах
минимально достижимое p знакового теста = 2⁻⁵ = 0.031, а после Холма по шести контрастам — 0.19,
то есть значимости не могло быть НИ ПРИ КАКОМ результате. Ось была недомощна ПО ПОСТРОЕНИЮ.
Норма фазы Д: у каждой оси напечатан MDE при плановом n, и ось, чей MDE не даёт несущего вывода,
объявляется описательной ЗАРАНЕЕ.
Два ограничения считаются раздельно, потому что упираются они в разное:
* ПОТОЛОК ЗНАЧИМОСТИ — минимально достижимое p точного знакового теста при n наблюдениях с
поправкой Холма по объявленному семейству. Если он ≥0.05, дизайн мёртв независимо от эффекта.
* MDE (минимально различимый эффект) — 2.8·sd/√n, то же правило, по которому риг печатает порог
различимости. Отвечает на «какой перевес прибор вообще способен развести».
⚠ sd берётся из ЗАМЕРЕННЫХ шумовых полов прошлых паков и помечается источником: своего пола у
будущего прохода ещё нет по построению. Плюс поправка ×1.23 на межсессионную компоненту — §12б
эксп-23 показал, что пол, чьи половины судит ОДИН судья, занижает порог на 19%.
Запуск: eval/.venv/bin/python eval/dovodka/power.py
"""
from __future__ import annotations
import math
import statistics
import sys
# Замеренные шумовые полы (sd разности по единицам) — источник указан у каждого.
# ⚠ ЯРЛЫКИ ИСПРАВЛЕНЫ РЕВЬЮ. Первая редакция называла пол `p3` «редакторы над одной базой» —
# это неверно: `p2zh` и `p3` разрешаются в ОДНИ И ТЕ ЖЕ 32 файла (`tp2-floor-p1/p2-E-*`), то есть
# оба меряют СКВОЗНУЮ связку «черновик+редактура», и оба несут те же две клетки `finish=length`.
# Разница 2.90 против 3.53 — чисто МЕЖСЕССИОННАЯ (×1.217), и это, кстати, СВОЯ оценка той самой
# компоненты, ради которой берётся заимствованная ×1.23 из §12б. Совпадение хорошее, но своя
# оценка теперь названа, а не умолчана.
FLOORS = {
"эксп-23 tier (редакторы над ОДНОЙ базой, zh, n=16)": 1.45,
"эксп-23 border (редакторы над ОДНОЙ базой, zh, n=16)": 2.12,
"эксп-22 p2zh (СКВОЗНАЯ связка, те же 32 файла, что p3)": 2.90,
"эксп-22 p3 (ТЕ ЖЕ файлы, другая судейская популяция)": 3.53,
}
BETWEEN = 1.23 # §12б эксп-23: наивная se → se с межсессионной компонентой
# ⚠ ЦЕЛЕВОЙ ЭФФЕКТ — то, ради чего ось покупается. Без него MDE не решает НИЧЕГО: печатать
# «MDE 1.83» и не сказать, меньше он искомого или больше, — это ровно та форма, за которую
# погорела en-ось эксп-22. Цель объявляется ДО денег и обосновывается замеренным, а не желаемым.
AXES = [
# (имя, план n, контрастов, sd-прайор, ЦЕЛЬ, обоснование прайора, обоснование цели)
("Д4 edit-контур zh", 32, 3, 2.12, 1.50,
"армы правят ОДНУ базу — это структура `tier`/`border` (sd 1.45/2.12), берётся худшая",
"решение «дешёвый фиксер вместо переписа» меняется, если разрыв ≥ трети собственного "
"эффекта редактора (+4.44 в эксп-22 Ф3) — это 1.50"),
("Д3 en→ru несущая", 16, 1, 2.12, 2.00,
"своего пола на en нет НИ ОДНОГО; прайор с zh той же структуры, пол докупается в фазе",
"несущий вопрос оси — переносится ли эффект редактора: на zh контроль +3.25, "
"значимым переносом считаем половину, 2.00"),
("Д6 ja→ru разведка", 9, 3, 2.90, 2.00,
"связка end-to-end (как p2zh) — шумнее",
"та же цель, что у en; ось объявлена ОПИСАТЕЛЬНОЙ промтом заранее и остаётся ею "
"независимо от арифметики"),
("Д1 gemini добивка", 16, 1, 2.12, 2.00,
"проход над той же якорной базой, что border — его пол ближайший",
"гипотеза эксп-04 «аутлаер прозы» — про КРУПНЫЙ отрыв; мелкий отрыв её не подтверждает "
"и продуктового смысла при цене 28× не имеет. ⚠ Именно этот порог решил спор о числе "
"клеток: при n=12 (минимум промта) MDE 2.11 БОЛЬШЕ цели и ось была бы ОПИСАТЕЛЬНОЙ; "
"владелец 10.08 выбрал полные 16, и только на них ось несущая"),
]
FORCED_DESCRIPTIVE = {"Д6 ja→ru разведка"} # объявлено промтом, арифметикой не отменяется
# ⚠ ПЕРВИЧНОЕ И ВТОРИЧНОЕ СЕМЕЙСТВА — объявляются ДО покупок, и это НЕ сужение задним числом.
# Поправка Холма делится на РАЗМЕР семейства, поэтому «взять все контрасты несущими» = сделать
# все недомощными. Пре-регистрируется так: первичное семейство несёт по одному контрасту на
# КАЖДУЮ гипотезу владельца и считается с Холмом; остальные контрасты объявлены ОПИСАТЕЛЬНЫМИ,
# печатаются с числами и поправку не несут. Ровно этот приём эксп-22 применил в §0-Ф2 к осям
# «черновик как текст», и он там сработал.
PRIMARY = {
"Д4 edit-контур zh": ["A1/A0 — флаговый контур (H-2а)",
"A3/A0 — смысловой контур (H-2б, ставка владельца)",
"A6/A0 — качественный черновик + контур (H-1)"],
"Д3 en→ru несущая": ["E0/D0 — КОНТРОЛЬ: покупает ли редактор поверх черновика (H-3)"],
"Д1 gemini добивка": ["R1/R0 — аутлаер ли проза gemini (гипотеза эксп-04)"],
}
SECONDARY = {
"Д4 edit-контур zh": ["A2/A0 — однопроходка уравненного мандата (строка 153 бэклога)",
"A4/A0 — канон-фиксер ПОСЛЕ переписа: его несущая ось — покрытие "
"канона, а оно детерминировано и шума не имеет"],
"Д3 en→ru несущая": ["E1..E5/E0 — армы контуров и однопроходки",
"E6/E0 — ВТОРОЙ редактор glm-5 (H-4): на n=16 порядок жильцов "
"разрешить нельзя, ось описательная и это сказано ДО денег"],
}
# ⚠ ПОЧЕМУ ПРАЙОР 2.12, А НЕ 2.90/3.53. Оба пола эксп-22 меряют СКВОЗНУЮ связку — они несут ещё и
# шум черновика, тогда как армы Д4 правят ОДНУ замороженную базу. Структурно ближе полы эксп-23
# (`tier` 1.45, `border` 2.12, оба над одной базой), и берётся ХУДШИЙ из них. Отдельно: обе пары
# пола эксп-22 содержат по две клетки `finish=length` (§15), что разброс завышает — без затронутых
# единиц тот же пол даёт 3.10; в фазе Д такие клетки в пачку не допускаются вовсе.
# ⚠ И честно: ПРАЙОР РЕШАЕТ. При sd 3.53 MDE оси Д4 был бы 2.48 против цели 1.50, то есть ось
# стала бы описательной. Прежняя редакция писала «прайор всё равно НЕ решает» — неверно, снято
# ревью. Решает он не окончательно: СВОЙ пол прохода меряется в фазе парой, чьи половины судят
# РАЗНЫЕ сессии, и если он выйдет хуже прайора — ось пере-классифицируется ТОГДА ЖЕ, до чтения
# боевых перевесов.
# Пространство дизайна: во что упирается вывод при разных n и разных полах.
GRID_N = (12, 16, 24, 32)
GRID_SD = (1.45, 2.12, 2.90, 3.53)
def min_p(n: int, k: int) -> float:
"""Минимально достижимое двустороннее p знакового теста при n, после Холма по k контрастам."""
return min(1.0, 2.0 ** (1 - n) * k)
def _z(p: float) -> float:
"""Квантиль стандартной нормали."""
return statistics.NormalDist().inv_cdf(p)
def mde(sd: float, n: int, k: int = 1, between: bool = True) -> float:
"""Минимально различимый эффект при 80% мощности и поправке Холма по k контрастам.
КОНСТАНТА 2.8 ВЕРНА ТОЛЬКО ПРИ k=1, и первая редакция этого файла применяла её всегда.
Поймано адверсариальным ревью снизу вверх. Решающее правило пака — `p` ХОЛМА < 0.05, то есть
на самый строгий контраст семейства приходится α/k; множитель становится
(z_{1α/2k} + z_{0.8}), при k=5 это 3.417 вместо 2.8. Пропущенная поправка ×1.221.36 БОЛЬШЕ
той ×1.23, которую файл старательно вносил за межсессионную компоненту, — и она переворачивала
вердикт: Д4 при k=5 давала MDE 1.57 против цели 1.50, то есть ОПИСАТЕЛЬНУЮ ось, объявленную
несущей. Это буквально провал en-оси эксп-22, ради недопущения которого файл и написан.
"""
mult = _z(1 - 0.05 / (2 * max(1, k))) + _z(0.80)
return mult * sd / math.sqrt(n) * (BETWEEN if between else 1.0)
def main() -> int:
print("ЗАМЕРЕННЫЕ ПОЛЫ, из которых берутся прайоры (sd разности по единицам):")
for k, v in FLOORS.items():
print(f" {k:54s} sd {v:.2f}")
print(f"\nпоправка на межсессионную компоненту: ×{BETWEEN} (§12б эксп-23)\n")
print(f"{'ось':24s}{'n':>4s}{'k':>3s}{'sd':>6s}{'MDE':>7s}{'ЦЕЛЬ':>7s}{'потолок p':>11s}"
" СТАТУС ОСИ")
desc = 0
for name, n, k, sd, target, _w1, _w2 in AXES:
p0 = min_p(n, k)
m_true = mde(sd, n, k)
# ⚠ ДВА УСЛОВИЯ, И ОБА НЕОБХОДИМЫ. (1) значимость достижима вообще; (2) прибор способен
# развести ИСКОМЫЙ эффект. Первая редакция смотрела только на (1) — и объявляла несущей
# ось, чей MDE вдвое больше того, что она ищет. Это и есть en-ось эксп-22, слово в слово.
why = []
if p0 >= 0.05:
why.append(f"значимость недостижима (потолок p {p0:.4f})")
if m_true > target:
why.append(f"MDE {m_true:.2f} БОЛЬШЕ искомого эффекта {target:.2f}")
if name in FORCED_DESCRIPTIVE:
why.append("объявлена описательной ЗАКАЗОМ")
status = "НЕСУЩАЯ" if not why else "⛔ ОПИСАТЕЛЬНАЯ: " + " · ".join(why)
desc += bool(why)
print(f"{name:24s}{n:4d}{k:3d}{sd:6.2f}{m_true:7.2f}{target:7.2f}{p0:11.4f} {status}")
print(f"\nописательных осей: {desc} из {len(AXES)} — объявлено ДО покупок, "
"пере-классификации задним числом не будет")
print("\nСЕМЕЙСТВА, объявленные ДО покупок:")
for name in [a[0] for a in AXES]:
if name in PRIMARY:
print(f" {name} — ПЕРВИЧНОЕ (Холм по {len(PRIMARY[name])}):")
for c in PRIMARY[name]:
print(f"{c}")
if name in SECONDARY:
print(" описательные (поправку не несут, печатаются с числами):")
for c in SECONDARY[name]:
print(f" · {c}")
print("\nОБОСНОВАНИЯ (прайор шума · искомый эффект):")
for name, _n, _k, _sd, _t, w1, w2 in AXES:
print(f" {name}\n прайор: {w1}\n цель: {w2}")
print("\nПРОСТРАНСТВО ДИЗАЙНА — честный MDE (с ×1.23) при разных n и разных полах:")
print(" " + "".join(f"{f'sd {s:.2f}':>10s}" for s in GRID_SD))
for n in GRID_N:
print(f" n={n:<4d}" + "".join(f"{mde(s, n, 1):10.2f}" for s in GRID_SD))
print(" (таблица при k=1; на семейство из k контрастов множитель растёт — см. mde())")
print("\n СКОЛЬКО ЕДИНИЦ НУЖНО, чтобы ось была несущей при sd 2.12 и цели:")
for k in (1, 2, 3, 4, 5):
for tgt in (1.50, 2.00):
need = math.ceil(((_z(1 - 0.05 / (2 * k)) + _z(0.80)) * 2.12 * BETWEEN / tgt) ** 2)
print(f" k={k} цель {tgt:.2f} → n ≥ {need}")
print("\n Для калибровки — РЕАЛЬНЫЕ перевесы, уже замеренные на этом материале:")
print(" эксп-22 Ф3: R2 2.12 · R3 2.54 · R4 4.75 · КОНТРОЛЬ +4.44")
print(" эксп-23 tier: T1 0.19 · T2 +0.50 · T3 +0.19 · КОНТРОЛЬ +2.06")
print(" эксп-23 border: R2 0.81")
print(" ⇒ Различия МЕЖДУ близкими редакторами лежат в полосе 0.22.5, то есть под MDE почти\n"
" любой строки этой таблицы. Развести их прибор способен только при малом поле И\n"
" большом n. Контрасты «есть ли редактура вообще» (+2.1…+4.4) берутся легко.")
print("\n⚠ Читать так: MDE — свойство ПЛАНА, а не результата. Свой пол каждого прохода меряется\n"
" в фазе и печатается рядом; если он окажется хуже прайора, ось пере-классифицируется в\n"
" описательную ТОГДА ЖЕ, до чтения боевых перевесов, а не после.")
print("\n⚠ Сравнение с эксп-22: его en-ось шла n=6 при k=6 ⇒ потолок p "
f"{min_p(6, 6):.4f} — дизайн не мог дать значимости. Здесь такой оси нет.")
return 0
if __name__ == "__main__":
sys.exit(main())