Fit succeeds with scipy, but fails with minuit #1695
-
Dear pyhf developers, for the attached workspace the fit with scipy succeeds, but fails with minuit. The workspace was imported from HistFitter, where the fit converges without issues. I have tried a variety of changes, including the For the minimum working example below, the code exists fine for Do you have any tip what could be going wrong? Some of the NPs have different types, could that be an issue? Versions: pyhf - master; iminuit - 2.8.2 Minimum usage examples: import pyhf
import json
jsonObj = None
with open("NormalMeasurement_combined.txt") as iF:
jsonObj = json.load(iF)
workspace = pyhf.Workspace(jsonObj)
useMinuit = 1
if useMinuit:
pyhf.set_backend("numpy", "minuit") ## get uncertainties
pyhf.set_backend(pyhf.tensorlib, pyhf.optimize.minuit_optimizer(tolerance=1,verbose=2,strategy=1))
model = workspace.model(measurement_name="NormalMeasurement")
data = workspace.data(model)
result = pyhf.infer.mle.fixed_poi_fit(1.0, data, model, return_uncertainties=True) |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 3 replies
-
ping @alexander-held . Not sure if you've tried changing the tolerance down to 1e-4 or something like that. Looking at your workspace, you seem to have most systematics with very little impact. |
Beta Was this translation helpful? Give feedback.
-
There have been some other documented instances of @gollumben If you just want something that will get you the import json
import numpy as np
import pyhf
if __name__ == "__main__":
backend_name = "numpy"
pyhf.set_backend(backend_name)
with open("NormalMeasurement_combined.json") as input_file:
workspace = pyhf.Workspace(json.load(input_file))
model = workspace.model(measurement_name="NormalMeasurement")
data = workspace.data(model)
test_poi = 1.0
fit_pars = pyhf.infer.mle.fixed_poi_fit(
test_poi, data, model, return_uncertainties=True
)
print(f"{fit_pars=}")
np.savetxt(f"fit_pars_{backend_name}.txt", fit_pars, delimiter=", ")
pyhf.set_backend(
backend_name,
pyhf.optimize.minuit_optimizer(tolerance=1, verbose=2, strategy=1),
)
fit_pars_minuit = pyhf.infer.mle.fixed_poi_fit(
test_poi,
data,
model,
init_pars=fit_pars.tolist(),
return_uncertainties=True,
)
print(f"{fit_pars_minuit=}")
np.savetxt(f"fit_pars_minuit_{backend_name}.txt", fit_pars_minuit, delimiter=", ")
pars_difference = fit_pars - fit_pars_minuit[:, 0]
np.savetxt(f"pars_difference_{backend_name}.txt", pars_difference, delimiter=", ") If I use JAX backend_name = "jax" I can complete both fits in under a minute on my laptop on CPU (and in 30 seconds on GPU). |
Beta Was this translation helpful? Give feedback.
-
Hi @kratsg and @matthewfeickert,
import jax
jax.devices()
Cheers, |
Beta Was this translation helpful? Give feedback.
-
Small observation: using the script provided at the top, I see a difference between the This may be related to the modifier changes introduced recently, the reordering might have introduced some tiny floating point changes which are enough to alter the path to the minimum such that 0.6.3 succeeds. |
Beta Was this translation helpful? Give feedback.
-
I managed to isolate a problem in this workspace, and expect that it's likely the full explanation for what you see. It seems to come down to the use of To get started, it is useful to have parameters where the NaN logpdf value happens. One way to do this is to add a breakpoint to
These are multiple The resulting model can be simplified even further by removing measurement config pieces that have no impact here. Here is what remains: import pyhf
spec = {
"channels": [
{
"name": "bin3_cuts",
"samples": [
{
"data": [3.490729331970215],
"modifiers": [
{
"data": [0.277386256344252],
"name": "staterror_bin3_cuts",
"type": "staterror",
},
{
"data": {
"hi_data": [3.8195865154266357],
"lo_data": [3.2028777599334717],
},
"name": "Sherpa_muR_zjets",
"type": "histosys",
},
{
"data": {
"hi_data": [3.546879291534424],
"lo_data": [3.3108692169189453],
},
"name": "WTag_BGSF_Gammajet_Stat",
"type": "histosys",
},
{
"data": {
"hi_data": [3.530710220336914],
"lo_data": [3.3270678520202637],
},
"name": "WTag_BGSF_Propagated_AllOthers",
"type": "histosys",
},
{
"data": {
"hi_data": [3.5470385551452637],
"lo_data": [3.3109240531921387],
},
"name": "WTag_JetTagSF_Dijet_Modelling",
"type": "histosys",
},
{
"data": {
"hi_data": [3.514037847518921],
"lo_data": [3.343658924102783],
},
"name": "WTag_JetTagSF_Radiation",
"type": "histosys",
},
{
"data": {
"hi_data": [3.5140371322631836],
"lo_data": [3.3436598777770996],
},
"name": "WTag_SigSF_BinVariation",
"type": "histosys",
},
],
"name": "zjetsBoosted",
}
],
}
],
"measurements": [
{"config": {"parameters": [], "poi": ""}, "name": "NormalMeasurement"}
],
"observations": [{"data": [3.0], "name": "bin3_cuts"}],
"version": "1.0.0",
}
ws = pyhf.Workspace(spec)
model = ws.model(poi_name=None)
data = ws.data(model)
# parameter values from other script
pars = [
-1.81622979,
-3.84084271,
-3.39250537,
-3.6207387,
-4.60027512,
-3.41855226,
0.934653809,
]
print(model.expected_data(pars, include_auxdata=False)) resulting in
which is a negative (unphysical) yield prediction. The setup in the full workspace causing a NaN may be slightly different from this, but I expect it comes down to the same. In principle other samples can cancel the negative prediction from this sample, but I expect jointly it is still something that plays out very similarly. Since the final fit results are unlikely to include such strong pulls, this is likely just a minimization issue that arises while the minimizer explores the parameter space. Different initialization can avoid this part of the parameter space and cause the result to be ok regardless. For stability reasons I would recommend the use of |
Beta Was this translation helpful? Give feedback.
I managed to isolate a problem in this workspace, and expect that it's likely the full explanation for what you see. It seems to come down to the use of
histosys
modifiers that have normalization effects (not just shape), but due to the way the HistFactory extrapolation works, this means that strong pulls can result in negative yield predictions. The exponential extrapolation fornormsys
modifiers protects against this, and I would recommend using it especially for single-bin cases, like the one causing an issue here.To get started, it is useful to have parameters where the NaN logpdf value happens. One way to do this is to add a breakpoint to
Model.logpdf
ifnp.isnan(result)
. From there…