3-стороннее сравнение ROOT/HistFactory
Комплексная валидация NextStat относительно pyhf (спецификация) и ROOT/RooFit (legacy-реализация) на канонических HistFactory-фикстурах. Согласие с pyhf по q(μ) лучше 1e-5 для всех фикстур.
Краткие итоги
| Фикстура | Модификаторы | NS vs pyhf: |dq(μ)| | NS vs ROOT: |dq(μ)| | Статус ROOT |
|---|---|---|---|---|
| xmlimport | OverallSys + StatError | 1e-7 | 0.051 | 0 (сошлось) |
| multichannel | ShapeSys | 4e-7 | 3.4e-8 | 0 (сошлось) |
| coupled_histosys | HistoSys (связанные мешающие параметры) | 5e-6 | 22.5 | -1 (ошибка) |
Экспорты TREx из практики
| Случай | NS vs ROOT: |dq(μ)| | Проблема ROOT |
|---|---|---|
| simple_fixture | 1.6e-10 | Нет (идеально) |
| histfactory_fixture | 1.89 | Расходимость оптимизатора |
| hepdata EWK | 0.0 | Разнос свободного фита (μ̂ = 4.9e23) |
| tttt-prod (249 params) | 0.04 | Проблемы сходимости в хвосте |
Методика
Каждая фикстура прогоняется через три независимых пайплайна, которые читают один и тот же XML и ROOT-гистограммы:
HistFactory XML + ROOT-гистограммы
│
├──► hist2workspace → RooFit → профильный скан ROOT (C++ через PyROOT)
├──► pyhf.readxml → pyhf → профильный скан pyhf (Python)
└──► импорт NextStat → PreparedModel → профильный скан NextStat (Rust)Профильный скан вычисляет q̃(μ) = 2·[NLL(μ) − NLL(μ̂)] в 31 равномерно распределенной точке по μ на отрезке μ = [0, 3]. Тестовая статистика: стандартная q_mu_tilde (Cowan et al., arXiv:1007.1727).
Подробное сравнение q(μ)
xmlimport: ROOT, NextStat и pyhf
| μ | ROOT q(μ) | pyhf q(μ) | NS q(μ) | NS − pyhf | ROOT − NS |
|---|---|---|---|---|---|
| 1.2 | 0.01957 | 0.01956 | 0.01956 | +1e-8 | +1e-5 |
| 2.0 | 2.07272 | 2.06669 | 2.06669 | −4e-7 | +6e-3 |
| 3.0 | 9.05788 | 9.00676 | 9.00676 | +1e-7 | +5.1e-2 |
NextStat и pyhf численно совпадают (Δ < 1e-6). ROOT систематически завышает q(μ) при больших μ, что согласуется с тем, что условный минимизатор Minuit2 сходится к немного более высокому значению NLL на экстремальных значениях параметров.
coupled_histosys: расходимость ROOT
| μ | ROOT q(μ) | pyhf q(μ) | NS q(μ) | NS − pyhf | ROOT − NS |
|---|---|---|---|---|---|
| 1.0 | 0.991 | 0.445 | 0.445 | +4e-6 | +0.545 |
| 2.0 | 15.526 | 6.543 | 6.543 | +5e-6 | +8.98 |
| 3.0 | 41.566 | 19.042 | 19.042 | +4e-6 | +22.52 |
NextStat и pyhf совпадают с точностью лучше < 1e-5. Начиная с μ = 1.0 ROOT дает принципиально другие результаты, и расхождение растет с μ. ROOT сообщает status_free = -1 (Minuit2 не смог определить положительно определенную ковариационную матрицу).
Почему ROOT расходится
Смещение NLL между ROOT и NextStat должно быть константой для всех значений μ (это параметронезависимая константа из члена ограничений). Для coupled_histosys:
| Точка | ROOT NLL | NS NLL | Смещение |
|---|---|---|---|
| Свободный фит | 434.754 | 14.017 | 420.737 |
| μ = 0.0 | 434.841 | 14.103 | 420.738 |
| μ = 2.0 | 442.517 | 17.288 | 425.229 |
| μ = 3.0 | 455.537 | 23.537 | 432.000 |
Смещение растет с 420.74 до 432.0. Это исключает чисто оптимизационную причину и указывает, что ROOT иначе вычисляет правдоподобие для связанного HistoSys при больших значениях alpha.
Сравнение времени
| Фикстура | ROOT | pyhf | NextStat | NS/ROOT | NS/pyhf |
|---|---|---|---|---|---|
| xmlimport | 0.91 s | 0.23 s | 0.003 s | 303× | 73× |
| multichannel | 1.98 s | 0.26 s | 0.007 s | 283× | 37× |
| coupled_histosys | 1.76 s | 0.15 s | 0.002 s | 880× | 75× |
Иерархия валидации
СПЕЦИФИКАЦИЯ (математическое определение, arXiv:1007.1727)
│
├── pyhf (референсная реализация ATLAS)
│ │
│ ├── NextStat ✓ < 1e-5 по q(μ), проверяется в CI
│ │
│ └── ROOT/RooFit
│ - ShapeSys: < 1e-6 (отлично)
│ - OverallSys: < 0.05 (эффект оптимизатора)
│ - Coupled HistoSys: РАСХОДИТСЯ (status=-1)
│ НЕ проверяется в CI (только справочно)Воспроизведение результатов
python tests/validate_root_profile_scan.py \
--histfactory-xml tests/fixtures/pyhf_xmlimport/config/example.xml \
--rootdir tests/fixtures/pyhf_xmlimport \
--include-pyhf --keep