We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
use strategy 0 for user-provided gradients in minuit
# Description

since we have exact gradient we can disable the checks minuit does

cc @alexander-held 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/pyhf/optimize/opt_minuit.py`
Content:
```
1 """Minuit Optimizer Class."""
2 from .. import default_backend, exceptions
3 from .mixins import OptimizerMixin
4 import scipy
5 import iminuit
6 
7 
8 class minuit_optimizer(OptimizerMixin):
9     """
10     Optimizer that uses iminuit.Minuit.migrad.
11     """
12 
13     __slots__ = ['name', 'errordef', 'steps']
14 
15     def __init__(self, *args, **kwargs):
16         """
17         Create MINUIT Optimizer.
18 
19         .. note::
20 
21             ``errordef`` should be 1.0 for a least-squares cost function and 0.5
22             for negative log-likelihood function. See page 37 of
23             http://hep.fi.infn.it/minuit.pdf. This parameter is sometimes
24             called ``UP`` in the ``MINUIT`` docs.
25 
26 
27         Args:
28             errordef (:obj:`float`): See minuit docs. Default is 1.0.
29             steps (:obj:`int`): Number of steps for the bounds. Default is 1000.
30         """
31         self.name = 'minuit'
32         self.errordef = kwargs.pop('errordef', 1)
33         self.steps = kwargs.pop('steps', 1000)
34         super().__init__(*args, **kwargs)
35 
36     def _get_minimizer(
37         self, objective_and_grad, init_pars, init_bounds, fixed_vals=None, do_grad=False
38     ):
39 
40         step_sizes = [(b[1] - b[0]) / float(self.steps) for b in init_bounds]
41         fixed_vals = fixed_vals or []
42         # Minuit wants True/False for each parameter
43         fixed_bools = [False] * len(init_pars)
44         for index, val in fixed_vals:
45             fixed_bools[index] = True
46             init_pars[index] = val
47             step_sizes[index] = 0.0
48 
49         # Minuit requires jac=callable
50         if do_grad:
51             wrapped_objective = lambda pars: objective_and_grad(pars)[0]
52             jac = lambda pars: objective_and_grad(pars)[1]
53         else:
54             wrapped_objective = objective_and_grad
55             jac = None
56 
57         kwargs = dict(
58             fcn=wrapped_objective,
59             grad=jac,
60             start=init_pars,
61             error=step_sizes,
62             limit=init_bounds,
63             fix=fixed_bools,
64             print_level=self.verbose,
65             errordef=self.errordef,
66         )
67         return iminuit.Minuit.from_array_func(**kwargs)
68 
69     def _minimize(
70         self,
71         minimizer,
72         func,
73         x0,
74         do_grad=False,
75         bounds=None,
76         fixed_vals=None,
77         return_uncertainties=False,
78         options={},
79     ):
80 
81         """
82         Same signature as :func:`scipy.optimize.minimize`.
83 
84         Note: an additional `minuit` is injected into the fitresult to get the
85         underlying minimizer.
86 
87         Minimizer Options:
88             maxiter (:obj:`int`): maximum number of iterations. Default is 100000.
89             return_uncertainties (:obj:`bool`): Return uncertainties on the fitted parameters. Default is off.
90 
91         Returns:
92             fitresult (scipy.optimize.OptimizeResult): the fit result
93         """
94         maxiter = options.pop('maxiter', self.maxiter)
95         return_uncertainties = options.pop('return_uncertainties', False)
96         if options:
97             raise exceptions.Unsupported(
98                 f"Unsupported options were passed in: {list(options.keys())}."
99             )
100 
101         minimizer.migrad(ncall=maxiter)
102         # Following lines below come from:
103         # https://github.com/scikit-hep/iminuit/blob/22f6ed7146c1d1f3274309656d8c04461dde5ba3/src/iminuit/_minimize.py#L106-L125
104         message = "Optimization terminated successfully."
105         if not minimizer.valid:
106             message = "Optimization failed."
107             fmin = minimizer.fmin
108             if fmin.has_reached_call_limit:
109                 message += " Call limit was reached."
110             if fmin.is_above_max_edm:
111                 message += " Estimated distance to minimum too large."
112 
113         n = len(x0)
114         hess_inv = default_backend.ones((n, n))
115         if minimizer.valid:
116             hess_inv = minimizer.np_covariance()
117 
118         unc = None
119         if return_uncertainties:
120             unc = minimizer.np_errors()
121 
122         return scipy.optimize.OptimizeResult(
123             x=minimizer.np_values(),
124             unc=unc,
125             success=minimizer.valid,
126             fun=minimizer.fval,
127             hess_inv=hess_inv,
128             message=message,
129             nfev=minimizer.ncalls,
130             njev=minimizer.ngrads,
131             minuit=minimizer,
132         )
133
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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
@@ -10,7 +10,7 @@
     Optimizer that uses iminuit.Minuit.migrad.
     """
 
-    __slots__ = ['name', 'errordef', 'steps']
+    __slots__ = ['name', 'errordef', 'steps', 'strategy']
 
     def __init__(self, *args, **kwargs):
         """
@@ -27,10 +27,12 @@
         Args:
             errordef (:obj:`float`): See minuit docs. Default is 1.0.
             steps (:obj:`int`): Number of steps for the bounds. Default is 1000.
+            strategy (:obj:`int`): See :attr:`iminuit.Minuit.strategy`. Default is None.
         """
         self.name = 'minuit'
         self.errordef = kwargs.pop('errordef', 1)
         self.steps = kwargs.pop('steps', 1000)
+        self.strategy = kwargs.pop('strategy', None)
         super().__init__(*args, **kwargs)
 
     def _get_minimizer(
@@ -87,17 +89,24 @@
         Minimizer Options:
             maxiter (:obj:`int`): maximum number of iterations. Default is 100000.
             return_uncertainties (:obj:`bool`): Return uncertainties on the fitted parameters. Default is off.
+            strategy (:obj:`int`): See :attr:`iminuit.Minuit.strategy`. Default is to configure in response to `do_grad`.
 
         Returns:
             fitresult (scipy.optimize.OptimizeResult): the fit result
         """
         maxiter = options.pop('maxiter', self.maxiter)
         return_uncertainties = options.pop('return_uncertainties', False)
+        # 0: Fast, user-provided gradient
+        # 1: Default, no user-provided gradient
+        strategy = options.pop(
+            'strategy', self.strategy if self.strategy else not do_grad
+        )
         if options:
             raise exceptions.Unsupported(
                 f"Unsupported options were passed in: {list(options.keys())}."
             )
 
+        minimizer.strategy = strategy
         minimizer.migrad(ncall=maxiter)
         # Following lines below come from:
         # https://github.com/scikit-hep/iminuit/blob/22f6ed7146c1d1f3274309656d8c04461dde5ba3/src/iminuit/_minimize.py#L106-L125
@@ -113,6 +122,8 @@
         n = len(x0)
         hess_inv = default_backend.ones((n, n))
         if minimizer.valid:
+            # Extra call to hesse() after migrad() is always needed for good error estimates. If you pass a user-provided gradient to MINUIT, convergence is faster.
+            minimizer.hesse()
             hess_inv = minimizer.np_covariance()
 
         unc = None

{"golden_diff": "diff --git a/src/pyhf/optimize/opt_minuit.py b/src/pyhf/optimize/opt_minuit.py\n--- a/src/pyhf/optimize/opt_minuit.py\n+++ b/src/pyhf/optimize/opt_minuit.py\n@@ -10,7 +10,7 @@\n     Optimizer that uses iminuit.Minuit.migrad.\n     \"\"\"\n \n-    __slots__ = ['name', 'errordef', 'steps']\n+    __slots__ = ['name', 'errordef', 'steps', 'strategy']\n \n     def __init__(self, *args, **kwargs):\n         \"\"\"\n@@ -27,10 +27,12 @@\n         Args:\n             errordef (:obj:`float`): See minuit docs. Default is 1.0.\n             steps (:obj:`int`): Number of steps for the bounds. Default is 1000.\n+            strategy (:obj:`int`): See :attr:`iminuit.Minuit.strategy`. Default is None.\n         \"\"\"\n         self.name = 'minuit'\n         self.errordef = kwargs.pop('errordef', 1)\n         self.steps = kwargs.pop('steps', 1000)\n+        self.strategy = kwargs.pop('strategy', None)\n         super().__init__(*args, **kwargs)\n \n     def _get_minimizer(\n@@ -87,17 +89,24 @@\n         Minimizer Options:\n             maxiter (:obj:`int`): maximum number of iterations. Default is 100000.\n             return_uncertainties (:obj:`bool`): Return uncertainties on the fitted parameters. Default is off.\n+            strategy (:obj:`int`): See :attr:`iminuit.Minuit.strategy`. Default is to configure in response to `do_grad`.\n \n         Returns:\n             fitresult (scipy.optimize.OptimizeResult): the fit result\n         \"\"\"\n         maxiter = options.pop('maxiter', self.maxiter)\n         return_uncertainties = options.pop('return_uncertainties', False)\n+        # 0: Fast, user-provided gradient\n+        # 1: Default, no user-provided gradient\n+        strategy = options.pop(\n+            'strategy', self.strategy if self.strategy else not do_grad\n+        )\n         if options:\n             raise exceptions.Unsupported(\n                 f\"Unsupported options were passed in: {list(options.keys())}.\"\n             )\n \n+        minimizer.strategy = strategy\n         minimizer.migrad(ncall=maxiter)\n         # Following lines below come from:\n         # https://github.com/scikit-hep/iminuit/blob/22f6ed7146c1d1f3274309656d8c04461dde5ba3/src/iminuit/_minimize.py#L106-L125\n@@ -113,6 +122,8 @@\n         n = len(x0)\n         hess_inv = default_backend.ones((n, n))\n         if minimizer.valid:\n+            # Extra call to hesse() after migrad() is always needed for good error estimates. If you pass a user-provided gradient to MINUIT, convergence is faster.\n+            minimizer.hesse()\n             hess_inv = minimizer.np_covariance()\n \n         unc = None\n", "issue": "use strategy 0 for user-provided gradients in minuit\n# Description\r\n\r\nsince we have exact gradient we can disable the checks minuit does\r\n\r\ncc @alexander-held \n", "before_files": [{"content": "\"\"\"Minuit Optimizer Class.\"\"\"\nfrom .. import default_backend, exceptions\nfrom .mixins import OptimizerMixin\nimport scipy\nimport iminuit\n\n\nclass minuit_optimizer(OptimizerMixin):\n    \"\"\"\n    Optimizer that uses iminuit.Minuit.migrad.\n    \"\"\"\n\n    __slots__ = ['name', 'errordef', 'steps']\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"\n        Create MINUIT Optimizer.\n\n        .. note::\n\n            ``errordef`` should be 1.0 for a least-squares cost function and 0.5\n            for negative log-likelihood function. See page 37 of\n            http://hep.fi.infn.it/minuit.pdf. This parameter is sometimes\n            called ``UP`` in the ``MINUIT`` docs.\n\n\n        Args:\n            errordef (:obj:`float`): See minuit docs. Default is 1.0.\n            steps (:obj:`int`): Number of steps for the bounds. Default is 1000.\n        \"\"\"\n        self.name = 'minuit'\n        self.errordef = kwargs.pop('errordef', 1)\n        self.steps = kwargs.pop('steps', 1000)\n        super().__init__(*args, **kwargs)\n\n    def _get_minimizer(\n        self, objective_and_grad, init_pars, init_bounds, fixed_vals=None, do_grad=False\n    ):\n\n        step_sizes = [(b[1] - b[0]) / float(self.steps) for b in init_bounds]\n        fixed_vals = fixed_vals or []\n        # Minuit wants True/False for each parameter\n        fixed_bools = [False] * len(init_pars)\n        for index, val in fixed_vals:\n            fixed_bools[index] = True\n            init_pars[index] = val\n            step_sizes[index] = 0.0\n\n        # Minuit requires jac=callable\n        if do_grad:\n            wrapped_objective = lambda pars: objective_and_grad(pars)[0]\n            jac = lambda pars: objective_and_grad(pars)[1]\n        else:\n            wrapped_objective = objective_and_grad\n            jac = None\n\n        kwargs = dict(\n            fcn=wrapped_objective,\n            grad=jac,\n            start=init_pars,\n            error=step_sizes,\n            limit=init_bounds,\n            fix=fixed_bools,\n            print_level=self.verbose,\n            errordef=self.errordef,\n        )\n        return iminuit.Minuit.from_array_func(**kwargs)\n\n    def _minimize(\n        self,\n        minimizer,\n        func,\n        x0,\n        do_grad=False,\n        bounds=None,\n        fixed_vals=None,\n        return_uncertainties=False,\n        options={},\n    ):\n\n        \"\"\"\n        Same signature as :func:`scipy.optimize.minimize`.\n\n        Note: an additional `minuit` is injected into the fitresult to get the\n        underlying minimizer.\n\n        Minimizer Options:\n            maxiter (:obj:`int`): maximum number of iterations. Default is 100000.\n            return_uncertainties (:obj:`bool`): Return uncertainties on the fitted parameters. Default is off.\n\n        Returns:\n            fitresult (scipy.optimize.OptimizeResult): the fit result\n        \"\"\"\n        maxiter = options.pop('maxiter', self.maxiter)\n        return_uncertainties = options.pop('return_uncertainties', False)\n        if options:\n            raise exceptions.Unsupported(\n                f\"Unsupported options were passed in: {list(options.keys())}.\"\n            )\n\n        minimizer.migrad(ncall=maxiter)\n        # Following lines below come from:\n        # https://github.com/scikit-hep/iminuit/blob/22f6ed7146c1d1f3274309656d8c04461dde5ba3/src/iminuit/_minimize.py#L106-L125\n        message = \"Optimization terminated successfully.\"\n        if not minimizer.valid:\n            message = \"Optimization failed.\"\n            fmin = minimizer.fmin\n            if fmin.has_reached_call_limit:\n                message += \" Call limit was reached.\"\n            if fmin.is_above_max_edm:\n                message += \" Estimated distance to minimum too large.\"\n\n        n = len(x0)\n        hess_inv = default_backend.ones((n, n))\n        if minimizer.valid:\n            hess_inv = minimizer.np_covariance()\n\n        unc = None\n        if return_uncertainties:\n            unc = minimizer.np_errors()\n\n        return scipy.optimize.OptimizeResult(\n            x=minimizer.np_values(),\n            unc=unc,\n            success=minimizer.valid,\n            fun=minimizer.fval,\n            hess_inv=hess_inv,\n            message=message,\n            nfev=minimizer.ncalls,\n            njev=minimizer.ngrads,\n            minuit=minimizer,\n        )\n", "path": "src/pyhf/optimize/opt_minuit.py"}], "after_files": [{"content": "\"\"\"Minuit Optimizer Class.\"\"\"\nfrom .. import default_backend, exceptions\nfrom .mixins import OptimizerMixin\nimport scipy\nimport iminuit\n\n\nclass minuit_optimizer(OptimizerMixin):\n    \"\"\"\n    Optimizer that uses iminuit.Minuit.migrad.\n    \"\"\"\n\n    __slots__ = ['name', 'errordef', 'steps', 'strategy']\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"\n        Create MINUIT Optimizer.\n\n        .. note::\n\n            ``errordef`` should be 1.0 for a least-squares cost function and 0.5\n            for negative log-likelihood function. See page 37 of\n            http://hep.fi.infn.it/minuit.pdf. This parameter is sometimes\n            called ``UP`` in the ``MINUIT`` docs.\n\n\n        Args:\n            errordef (:obj:`float`): See minuit docs. Default is 1.0.\n            steps (:obj:`int`): Number of steps for the bounds. Default is 1000.\n            strategy (:obj:`int`): See :attr:`iminuit.Minuit.strategy`. Default is None.\n        \"\"\"\n        self.name = 'minuit'\n        self.errordef = kwargs.pop('errordef', 1)\n        self.steps = kwargs.pop('steps', 1000)\n        self.strategy = kwargs.pop('strategy', None)\n        super().__init__(*args, **kwargs)\n\n    def _get_minimizer(\n        self, objective_and_grad, init_pars, init_bounds, fixed_vals=None, do_grad=False\n    ):\n\n        step_sizes = [(b[1] - b[0]) / float(self.steps) for b in init_bounds]\n        fixed_vals = fixed_vals or []\n        # Minuit wants True/False for each parameter\n        fixed_bools = [False] * len(init_pars)\n        for index, val in fixed_vals:\n            fixed_bools[index] = True\n            init_pars[index] = val\n            step_sizes[index] = 0.0\n\n        # Minuit requires jac=callable\n        if do_grad:\n            wrapped_objective = lambda pars: objective_and_grad(pars)[0]\n            jac = lambda pars: objective_and_grad(pars)[1]\n        else:\n            wrapped_objective = objective_and_grad\n            jac = None\n\n        kwargs = dict(\n            fcn=wrapped_objective,\n            grad=jac,\n            start=init_pars,\n            error=step_sizes,\n            limit=init_bounds,\n            fix=fixed_bools,\n            print_level=self.verbose,\n            errordef=self.errordef,\n        )\n        return iminuit.Minuit.from_array_func(**kwargs)\n\n    def _minimize(\n        self,\n        minimizer,\n        func,\n        x0,\n        do_grad=False,\n        bounds=None,\n        fixed_vals=None,\n        return_uncertainties=False,\n        options={},\n    ):\n\n        \"\"\"\n        Same signature as :func:`scipy.optimize.minimize`.\n\n        Note: an additional `minuit` is injected into the fitresult to get the\n        underlying minimizer.\n\n        Minimizer Options:\n            maxiter (:obj:`int`): maximum number of iterations. Default is 100000.\n            return_uncertainties (:obj:`bool`): Return uncertainties on the fitted parameters. Default is off.\n            strategy (:obj:`int`): See :attr:`iminuit.Minuit.strategy`. Default is to configure in response to `do_grad`.\n\n        Returns:\n            fitresult (scipy.optimize.OptimizeResult): the fit result\n        \"\"\"\n        maxiter = options.pop('maxiter', self.maxiter)\n        return_uncertainties = options.pop('return_uncertainties', False)\n        # 0: Fast, user-provided gradient\n        # 1: Default, no user-provided gradient\n        strategy = options.pop(\n            'strategy', self.strategy if self.strategy else not do_grad\n        )\n        if options:\n            raise exceptions.Unsupported(\n                f\"Unsupported options were passed in: {list(options.keys())}.\"\n            )\n\n        minimizer.strategy = strategy\n        minimizer.migrad(ncall=maxiter)\n        # Following lines below come from:\n        # https://github.com/scikit-hep/iminuit/blob/22f6ed7146c1d1f3274309656d8c04461dde5ba3/src/iminuit/_minimize.py#L106-L125\n        message = \"Optimization terminated successfully.\"\n        if not minimizer.valid:\n            message = \"Optimization failed.\"\n            fmin = minimizer.fmin\n            if fmin.has_reached_call_limit:\n                message += \" Call limit was reached.\"\n            if fmin.is_above_max_edm:\n                message += \" Estimated distance to minimum too large.\"\n\n        n = len(x0)\n        hess_inv = default_backend.ones((n, n))\n        if minimizer.valid:\n            # Extra call to hesse() after migrad() is always needed for good error estimates. If you pass a user-provided gradient to MINUIT, convergence is faster.\n            minimizer.hesse()\n            hess_inv = minimizer.np_covariance()\n\n        unc = None\n        if return_uncertainties:\n            unc = minimizer.np_errors()\n\n        return scipy.optimize.OptimizeResult(\n            x=minimizer.np_values(),\n            unc=unc,\n            success=minimizer.valid,\n            fun=minimizer.fval,\n            hess_inv=hess_inv,\n            message=message,\n            nfev=minimizer.ncalls,\n            njev=minimizer.ngrads,\n            minuit=minimizer,\n        )\n", "path": "src/pyhf/optimize/opt_minuit.py"}]}