feat: Improvements based on arXiv:2504.07686 files#187
Conversation
|
One quick note --
For this one, see #109 (comment) . An array of zeros is the correct choice. One needs to preserve round-tripping by not having the error written out for the sample if it is an array of all zeros as well.
Related to hep-statistics-serialization-standard/hep-statistics-serialization-standard#68 (comment) . For For additional support of other constraints, it would be great to get this (related: #138) . I had a plan which was to just generate the mathematical expressions in ROOT and print them out and port them over, since the existing documentation here is not abundantly clear on how to ensure they are implemented correctly.
This is not in the HS3 spec -- https://hep-statistics-serialization-standard.github.io/parts/histfactory/. If you're using AI, make sure you acknowledge which one. And make sure all tests pass and coverage is 100% and no type issues. |
|
Issue 1 can be fixed. |
These changes are based on the HS3 file provided with arXiv:2504.07686. can be downloaded via
@kratsg, please let me know if these break generality. I'm trying to work on a case-by-case basis.
Error summary
Running
pyhs3.Workspace(**ws_dict)onlikelihood-dphijj-cp.jsonraises a billion pydanticValidationErrorstaterrormodifier missing requireddatafieldStatErrorModifierSampleData.errorsfield missing (optional in HS3)SampleData"custom"ModifierTypeunionIssue 1:
staterrormodifier:datafield incorrectly required{ "constraint_type": "Poisson", "name": "staterror", "parameters": [ "auto_gamma_stat_CR_0j_DF_DNN_ptH_0_10_WW_bin_0_HWW_ggFVBF_01jDF_ggFVBF_01jDF" ], "type": "staterror" }The modifier specifies the per-bin gamma parameter names in
parametersand the constraint type viaconstraint_type, but carries nodataobject. The per-bin statistical uncertainties are instead stored on the sample's own data:StatErrorModifier(inmodifiers.py) is defined as:StatErrorDatarequires:Because there is no
datakey in the file'sstaterrorobject, Pydantic raisesField requireda billion times.HS3 standard
The HS3 v0.2.9 specification (§HistFactory) describes
staterroras:Crucially, the spec's normative example for
staterrorcontains onlyconstraintandparameterskeys nodatafield. The per-bin uncertainties are not embedded in the modifier; they are taken implicitly from the parent sample'serrorsarray (the Barlow-Beeston method). Requiring an explicitdata.uncertaintiesblock in the modifier is therefore an internal pyHS3 design choice that conflicts with the standard.Two further discrepancies exist between the ATLAS file and phys3:
constraint_typevsconstraint: The ATLAS file usesconstraint_type: "Poisson", which matches the HS3 spec field name. pyhs3'sStatErrorModifieruses the internal attributeconstraintand hardcodes it to"Gauss"`, so even if the field were parsed, it would be silently ignored.staterror: The spec explicitly allows"Poisson"as a valid constraint type forstaterror, but pyhs3 only acceptsLiteral["Gauss"].Issue 2:
SampleData.errorsincorrectly requiredMany samples omit the
errorskey entirely:Because Pydantic enforces this strictly, every sample without
errorstriggers aField requiredvalidation error.The HS3 v0.2.9 specification describes sample
dataas:While both fields are named, the spec does not mark
errorsas normatively required; it describes what the field represents rather than mandating its presence. Real-world ATLAS workspaces routinely omiterrorsfor samples where MC statistical uncertainties are negligible or handled via a globalstaterror. When absent, the interpretation is that per-bin errors are zero (infinite MC statistics).Issue 3:
custommodifier type not implemented{ "name": "combPdf_16", "type": "custom" }The modifier's
nameis the identifier of an entry in the top-levelfunctionssection. For this file, every such reference resolves to ageneric_function:{ "expression": "mu_VBF_qq2Hqq_1J_param", "name": "combPdf_16", "type": "generic_function" }In other words,
customis an indirection mechanism: instead of embedding normalisation logic inside the modifier itself, the modifier delegates to a named function that is already registered in the workspace. This allows the reuse of complex normalisation expressions (e.g. multi-POI signal strengths, combined normalisation products) across many samples without duplication.Changes made
Fix 1: distributions/histfactory/samples.py
errors: list[float] → errors: list[float] | None = None, with a model_validator that fills zeros when absent. The test test_sampledata_requires_errors was updated to assert the new correct behaviour (defaults to zeros).Fix 2: distributions/histfactory/modifiers.py
StatErrorModifier three sub-fixes
data: StatErrorData→data: StatErrorData | None = None. When absent, make_constraint derives uncertainties from the parent sample_data.errors array (the Barlow-Beeston path described in the HS3 spec).constraint_typealias: amodel_validator(mode='before')maps the ATLAS field name constraint_type → internal constraint before pydantic parses the model.Literal["Gauss"]→Literal["Gauss", "Poisson"], with a separate code path in make_constraint that builds Poisson Barlow-Beeston constraints (gamma × tau ~ Poisson(tau)) instead of the Gaussian path.Fix 3: distributions/histfactory/modifiers.py
context[self.name]. Registered at the front of theModifierTypediscriminated union.