NextStatNextStat

3-стороннее сравнение ROOT/HistFactory

Комплексная валидация NextStat относительно pyhf (спецификация) и ROOT/RooFit (legacy-реализация) на канонических HistFactory-фикстурах. Согласие с pyhf по q(μ) лучше 1e-5 для всех фикстур.

Краткие итоги

ФикстураМодификаторыNS vs pyhf: |dq(μ)|NS vs ROOT: |dq(μ)|Статус ROOT
xmlimportOverallSys + StatError1e-70.0510 (сошлось)
multichannelShapeSys4e-73.4e-80 (сошлось)
coupled_histosysHistoSys (связанные мешающие параметры)5e-622.5-1 (ошибка)

Экспорты TREx из практики

СлучайNS vs ROOT: |dq(μ)|Проблема ROOT
simple_fixture1.6e-10Нет (идеально)
histfactory_fixture1.89Расходимость оптимизатора
hepdata EWK0.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 − pyhfROOT − NS
1.20.019570.019560.01956+1e-8+1e-5
2.02.072722.066692.06669−4e-7+6e-3
3.09.057889.006769.00676+1e-7+5.1e-2

NextStat и pyhf численно совпадают (Δ < 1e-6). ROOT систематически завышает q(μ) при больших μ, что согласуется с тем, что условный минимизатор Minuit2 сходится к немного более высокому значению NLL на экстремальных значениях параметров.

coupled_histosys: расходимость ROOT

μROOT q(μ)pyhf q(μ)NS q(μ)NS − pyhfROOT − NS
1.00.9910.4450.445+4e-6+0.545
2.015.5266.5436.543+5e-6+8.98
3.041.56619.04219.042+4e-6+22.52

NextStat и pyhf совпадают с точностью лучше < 1e-5. Начиная с μ = 1.0 ROOT дает принципиально другие результаты, и расхождение растет с μ. ROOT сообщает status_free = -1 (Minuit2 не смог определить положительно определенную ковариационную матрицу).

Почему ROOT расходится

Смещение NLL между ROOT и NextStat должно быть константой для всех значений μ (это параметронезависимая константа из члена ограничений). Для coupled_histosys:

ТочкаROOT NLLNS NLLСмещение
Свободный фит434.75414.017420.737
μ = 0.0434.84114.103420.738
μ = 2.0442.51717.288425.229
μ = 3.0455.53723.537432.000

Смещение растет с 420.74 до 432.0. Это исключает чисто оптимизационную причину и указывает, что ROOT иначе вычисляет правдоподобие для связанного HistoSys при больших значениях alpha.

Сравнение времени

ФикстураROOTpyhfNextStatNS/ROOTNS/pyhf
xmlimport0.91 s0.23 s0.003 s303×73×
multichannel1.98 s0.26 s0.007 s283×37×
coupled_histosys1.76 s0.15 s0.002 s880×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