Repository: scikit-hep/pyhf

Issue:
CI failing for `iminuit` `v2.12.2`
### Summary

On 2022-07-15 [`iminuit` `v2.12.2`](https://github.com/scikit-hep/iminuit/releases/tag/v2.12.2) was released to "fix bug in error heuristic and prevent assigning non-positive values". With this release the CI is failing for `tests/test_infer.py`, `tests/test_otim.py`, `tests/test_jit.py`, `tests/test_public_api.py`, `tests/test_scripts.py`, and `tests/test_validation.py`

### OS / Environment

```console
All
```


### Steps to Reproduce

Run the test suite in CI


### File Upload (optional)

_No response_

### Expected Results

CI to pass

### Actual Results

```console
All the above tests fail.
```


### pyhf Version

```console
`HEAD` which is currently 8e242c9c829963c714f9b10acd28494f004a2452
```


### Code of Conduct

- [X] I agree to follow the Code of Conduct


Discussion:
I saw the same for `cabinetry`, presumably due to https://github.com/scikit-hep/iminuit/issues/762.

Resolving patch:
diff --git a/src/pyhf/optimize/mixins.py b/src/pyhf/optimize/mixins.py
--- a/src/pyhf/optimize/mixins.py
+++ b/src/pyhf/optimize/mixins.py
@@ -1,9 +1,11 @@
 """Helper Classes for use of automatic differentiation."""
-from pyhf.tensor.manager import get_backend
+import logging
+
+import numpy as np
+
 from pyhf import exceptions
 from pyhf.optimize.common import shim
-
-import logging
+from pyhf.tensor.manager import get_backend
 
 log = logging.getLogger(__name__)
 
@@ -82,6 +84,13 @@ def _internal_postprocess(self, fitresult, stitch_pars, return_uncertainties=Fal
         if uncertainties is not None:
             # extract number of fixed parameters
             num_fixed_pars = len(fitted_pars) - len(fitresult.x)
+
+            # FIXME: Set uncertainties for fixed parameters to 0 manually
+            # https://github.com/scikit-hep/iminuit/issues/762
+            # https://github.com/scikit-hep/pyhf/issues/1918
+            # https://github.com/scikit-hep/cabinetry/pull/346
+            uncertainties = np.where(fitresult.minuit.fixed, 0.0, uncertainties)
+
             # stitch in zero-uncertainty for fixed values
             uncertainties = stitch_pars(
                 tensorlib.astensor(uncertainties),
diff --git a/src/pyhf/optimize/opt_minuit.py b/src/pyhf/optimize/opt_minuit.py
--- a/src/pyhf/optimize/opt_minuit.py
+++ b/src/pyhf/optimize/opt_minuit.py
@@ -50,14 +50,12 @@ def _get_minimizer(
         par_names=None,
     ):
 
-        step_sizes = [(b[1] - b[0]) / float(self.steps) for b in init_bounds]
         fixed_vals = fixed_vals or []
         # Minuit wants True/False for each parameter
         fixed_bools = [False] * len(init_pars)
         for index, val in fixed_vals:
             fixed_bools[index] = True
             init_pars[index] = val
-            step_sizes[index] = 0.0
 
         # Minuit requires jac=callable
         if do_grad:
@@ -68,7 +66,6 @@ def _get_minimizer(
             jac = None
 
         minuit = iminuit.Minuit(wrapped_objective, init_pars, grad=jac, name=par_names)
-        minuit.errors = step_sizes
         minuit.limits = init_bounds
         minuit.fixed = fixed_bools
         minuit.print_level = self.verbose


Test patch:
diff --git a/tests/test_optim.py b/tests/test_optim.py
--- a/tests/test_optim.py
+++ b/tests/test_optim.py
@@ -520,20 +520,6 @@ def test_init_pars_sync_fixed_values_minuit(mocker):
     assert minimizer.fixed == [True, False, False]
 
 
-def test_step_sizes_fixed_parameters_minuit(mocker):
-    opt = pyhf.optimize.minuit_optimizer()
-
-    # patch all we need
-    from pyhf.optimize import opt_minuit
-
-    minuit = mocker.patch.object(getattr(opt_minuit, 'iminuit'), 'Minuit')
-    minimizer = opt._get_minimizer(None, [9, 9, 9], [(0, 10)] * 3, fixed_vals=[(0, 1)])
-
-    assert minuit.called
-    assert minimizer.fixed == [True, False, False]
-    assert minimizer.errors == [0.0, 0.01, 0.01]
-
-
 def test_solver_options_behavior_scipy(mocker):
     opt = pyhf.optimize.scipy_optimizer(solver_options={'arbitrary_option': 'foobar'})