We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Parameter bounds affect fits with fixed parameters
# Description

See this example:
```python
import pyhf

spec = {
    "channels": [
        {
            "name": "SR",
            "samples": [
                {
                    "data": [10.0],
                    "modifiers": [
                        {"data": None, "name": "Signal_norm", "type": "normfactor"}
                    ],
                    "name": "Signal",
                },
                {"data": [50.0], "modifiers": [], "name": "Background"},
            ],
        }
    ],
    "measurements": [
        {
            "config": {
                "parameters": [
                    {"bounds": [[0, 5]], "inits": [2.0], "name": "Signal_norm"}
                ],
                "poi": "Signal_norm",
            },
            "name": "minimal_example",
        }
    ],
    "observations": [{"data": [65.0], "name": "SR"}],
    "version": "1.0.0",
}

ws = pyhf.Workspace(spec)
model = ws.model()
data = ws.data(model)

# pyhf.infer.hypotest(6.0, data, model, qtilde=True)  # this fails

pyhf.set_backend("numpy", pyhf.optimize.minuit_optimizer(verbose=False))
# the next two work, but result in identical values
print(pyhf.infer.hypotest(6.0, data, model, qtilde=True))
print(pyhf.infer.hypotest(7.0, data, model, qtilde=True))
```

The CLs value reported in the last two lines is identical. This seems to be related to the parameter bounds `[0,5]`. When increasing the range, the CLs values are different (as expected).

When uncommenting the first `hypotest` (using `scipy`), the minimization fails:
```
pyhf.exceptions.FailedMinimization: Positive directional derivative for linesearch
```

# Expected Behavior

All fits succeed, and different CLs for any POI value. If the bounds are somehow still respected, a warning about parameters being outside the bounds (and scaled back to the bound) would be helpful.

# Actual Behavior

Same CLs for POI values above bound, and `scipy` backend crashes.

# Steps to Reproduce

See above, same behavior with 0.5.2 and master.

# Checklist

- [X] Run `git fetch` to get the most up to date version of `master`
- [X] Searched through existing Issues to confirm this is not a duplicate issue
- [X] Filled out the Description, Expected Behavior, Actual Behavior, and Steps to Reproduce sections above or have edited/removed them in a way that fully describes the issue


--- 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/infer/mle.py`
Content:
```
1 """Module for Maximum Likelihood Estimation."""
2 from .. import get_backend
3 from ..exceptions import UnspecifiedPOI
4 
5 
6 def twice_nll(pars, data, pdf):
7     r"""
8     Two times the negative log-likelihood of the model parameters, :math:`\left(\mu, \boldsymbol{\theta}\right)`, given the observed data.
9     It is used in the calculation of the test statistic, :math:`t_{\mu}`, as defiend in Equation (8) in :xref:`arXiv:1007.1727`
10 
11     .. math::
12 
13        t_{\mu} = -2\ln\lambda\left(\mu\right)
14 
15     where :math:`\lambda\left(\mu\right)` is the profile likelihood ratio as defined in Equation (7)
16 
17     .. math::
18 
19        \lambda\left(\mu\right) = \frac{L\left(\mu, \hat{\hat{\boldsymbol{\theta}}}\right)}{L\left(\hat{\mu}, \hat{\boldsymbol{\theta}}\right)}\,.
20 
21     It serves as the objective function to minimize in :func:`~pyhf.infer.mle.fit`
22     and :func:`~pyhf.infer.mle.fixed_poi_fit`.
23 
24     Example:
25         >>> import pyhf
26         >>> pyhf.set_backend("numpy")
27         >>> model = pyhf.simplemodels.hepdata_like(
28         ...     signal_data=[12.0, 11.0], bkg_data=[50.0, 52.0], bkg_uncerts=[3.0, 7.0]
29         ... )
30         >>> observations = [51, 48]
31         >>> data = pyhf.tensorlib.astensor(observations + model.config.auxdata)
32         >>> parameters = model.config.suggested_init()  # nominal parameters
33         >>> twice_nll = pyhf.infer.mle.twice_nll(parameters, data, model)
34         >>> twice_nll
35         array([30.77525435])
36         >>> -2 * model.logpdf(parameters, data) == twice_nll
37         array([ True])
38 
39     Args:
40         pars (:obj:`tensor`): The parameters of the HistFactory model
41         data (:obj:`tensor`): The data to be considered
42         pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json
43 
44     Returns:
45         Tensor: Two times the negative log-likelihood, :math:`-2\ln L\left(\mu, \boldsymbol{\theta}\right)`
46     """
47     return -2 * pdf.logpdf(pars, data)
48 
49 
50 def fit(data, pdf, init_pars=None, par_bounds=None, fixed_params=None, **kwargs):
51     r"""
52     Run a maximum likelihood fit.
53     This is done by minimizing the objective function :func:`~pyhf.infer.mle.twice_nll`
54     of the model parameters given the observed data.
55     This is used to produce the maximal likelihood :math:`L\left(\hat{\mu}, \hat{\boldsymbol{\theta}}\right)`
56     in the profile likelihood ratio in Equation (7) in :xref:`arXiv:1007.1727`
57 
58     .. math::
59 
60        \lambda\left(\mu\right) = \frac{L\left(\mu, \hat{\hat{\boldsymbol{\theta}}}\right)}{L\left(\hat{\mu}, \hat{\boldsymbol{\theta}}\right)}
61 
62 
63     .. note::
64 
65         :func:`twice_nll` is the objective function given to the optimizer and
66         is returned evaluated at the best fit model parameters when the optional
67         kwarg ``return_fitted_val`` is ``True``.
68 
69     Example:
70         >>> import pyhf
71         >>> pyhf.set_backend("numpy")
72         >>> model = pyhf.simplemodels.hepdata_like(
73         ...     signal_data=[12.0, 11.0], bkg_data=[50.0, 52.0], bkg_uncerts=[3.0, 7.0]
74         ... )
75         >>> observations = [51, 48]
76         >>> data = pyhf.tensorlib.astensor(observations + model.config.auxdata)
77         >>> bestfit_pars, twice_nll = pyhf.infer.mle.fit(data, model, return_fitted_val=True)
78         >>> bestfit_pars
79         array([0.        , 1.0030512 , 0.96266961])
80         >>> twice_nll
81         array(24.98393521)
82         >>> -2 * model.logpdf(bestfit_pars, data) == twice_nll
83         array([ True])
84 
85     Args:
86         data (:obj:`tensor`): The data
87         pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json
88         init_pars (:obj:`list`): Values to initialize the model parameters at for the fit
89         par_bounds (:obj:`list` of :obj:`list`\s or :obj:`tuple`\s): The extrema of values the model parameters are allowed to reach in the fit
90         fixed_params (:obj:`list`): Parameters to be held constant in the fit.
91         kwargs: Keyword arguments passed through to the optimizer API
92 
93     Returns:
94         See optimizer API
95 
96     """
97     _, opt = get_backend()
98     init_pars = init_pars or pdf.config.suggested_init()
99     par_bounds = par_bounds or pdf.config.suggested_bounds()
100     fixed_params = fixed_params or pdf.config.suggested_fixed()
101 
102     # get fixed vals from the model
103     fixed_vals = [
104         (index, init)
105         for index, (init, is_fixed) in enumerate(zip(init_pars, fixed_params))
106         if is_fixed
107     ]
108 
109     return opt.minimize(
110         twice_nll, data, pdf, init_pars, par_bounds, fixed_vals, **kwargs
111     )
112 
113 
114 def fixed_poi_fit(
115     poi_val, data, pdf, init_pars=None, par_bounds=None, fixed_params=None, **kwargs
116 ):
117     r"""
118     Run a maximum likelihood fit with the POI value fixed.
119     This is done by minimizing the objective function of :func:`~pyhf.infer.mle.twice_nll`
120     of the model parameters given the observed data, for a given fixed value of :math:`\mu`.
121     This is used to produce the constrained maximal likelihood for the given :math:`\mu`
122     :math:`L\left(\mu, \hat{\hat{\boldsymbol{\theta}}}\right)` in the profile
123     likelihood ratio in Equation (7) in :xref:`arXiv:1007.1727`
124 
125     .. math::
126 
127        \lambda\left(\mu\right) = \frac{L\left(\mu, \hat{\hat{\boldsymbol{\theta}}}\right)}{L\left(\hat{\mu}, \hat{\boldsymbol{\theta}}\right)}
128 
129     .. note::
130 
131         :func:`twice_nll` is the objective function given to the optimizer and
132         is returned evaluated at the best fit model parameters when the optional
133         kwarg ``return_fitted_val`` is ``True``.
134 
135     Example:
136         >>> import pyhf
137         >>> pyhf.set_backend("numpy")
138         >>> model = pyhf.simplemodels.hepdata_like(
139         ...     signal_data=[12.0, 11.0], bkg_data=[50.0, 52.0], bkg_uncerts=[3.0, 7.0]
140         ... )
141         >>> observations = [51, 48]
142         >>> data = pyhf.tensorlib.astensor(observations + model.config.auxdata)
143         >>> test_poi = 1.0
144         >>> bestfit_pars, twice_nll = pyhf.infer.mle.fixed_poi_fit(
145         ...     test_poi, data, model, return_fitted_val=True
146         ... )
147         >>> bestfit_pars
148         array([1.        , 0.97224597, 0.87553894])
149         >>> twice_nll
150         array(28.92218013)
151         >>> -2 * model.logpdf(bestfit_pars, data) == twice_nll
152         array([ True])
153 
154     Args:
155         data: The data
156         pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json
157         init_pars (:obj:`list`): Values to initialize the model parameters at for the fit
158         par_bounds (:obj:`list` of :obj:`list`\s or :obj:`tuple`\s): The extrema of values the model parameters are allowed to reach in the fit
159         fixed_params (:obj:`list`): Parameters to be held constant in the fit.
160         kwargs: Keyword arguments passed through to the optimizer API
161 
162     Returns:
163         See optimizer API
164 
165     """
166     if pdf.config.poi_index is None:
167         raise UnspecifiedPOI(
168             'No POI is defined. A POI is required to fit with a fixed POI.'
169         )
170 
171     init_pars = [*(init_pars or pdf.config.suggested_init())]
172     fixed_params = [*(fixed_params or pdf.config.suggested_fixed())]
173 
174     init_pars[pdf.config.poi_index] = poi_val
175     fixed_params[pdf.config.poi_index] = True
176 
177     return fit(data, pdf, init_pars, par_bounds, fixed_params, **kwargs)
178
```
--- 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/infer/mle.py b/src/pyhf/infer/mle.py
--- a/src/pyhf/infer/mle.py
+++ b/src/pyhf/infer/mle.py
@@ -47,6 +47,17 @@
     return -2 * pdf.logpdf(pars, data)
 
 
+def _validate_fit_inputs(init_pars, par_bounds, fixed_params):
+    for par_idx, (value, bound) in enumerate(zip(init_pars, par_bounds)):
+        if not (bound[0] <= value <= bound[1]):
+            raise ValueError(
+                f"fit initialization parameter (index: {par_idx}, value: {value}) lies outside of its bounds: {bound}"
+                + "\nTo correct this adjust the initialization parameter values in the model spec or those given"
+                + "\nas arguments to pyhf.infer.fit. If this value is intended, adjust the range of the parameter"
+                + "\nbounds."
+            )
+
+
 def fit(data, pdf, init_pars=None, par_bounds=None, fixed_params=None, **kwargs):
     r"""
     Run a maximum likelihood fit.
@@ -99,6 +110,8 @@
     par_bounds = par_bounds or pdf.config.suggested_bounds()
     fixed_params = fixed_params or pdf.config.suggested_fixed()
 
+    _validate_fit_inputs(init_pars, par_bounds, fixed_params)
+
     # get fixed vals from the model
     fixed_vals = [
         (index, init)

{"golden_diff": "diff --git a/src/pyhf/infer/mle.py b/src/pyhf/infer/mle.py\n--- a/src/pyhf/infer/mle.py\n+++ b/src/pyhf/infer/mle.py\n@@ -47,6 +47,17 @@\n     return -2 * pdf.logpdf(pars, data)\n \n \n+def _validate_fit_inputs(init_pars, par_bounds, fixed_params):\n+    for par_idx, (value, bound) in enumerate(zip(init_pars, par_bounds)):\n+        if not (bound[0] <= value <= bound[1]):\n+            raise ValueError(\n+                f\"fit initialization parameter (index: {par_idx}, value: {value}) lies outside of its bounds: {bound}\"\n+                + \"\\nTo correct this adjust the initialization parameter values in the model spec or those given\"\n+                + \"\\nas arguments to pyhf.infer.fit. If this value is intended, adjust the range of the parameter\"\n+                + \"\\nbounds.\"\n+            )\n+\n+\n def fit(data, pdf, init_pars=None, par_bounds=None, fixed_params=None, **kwargs):\n     r\"\"\"\n     Run a maximum likelihood fit.\n@@ -99,6 +110,8 @@\n     par_bounds = par_bounds or pdf.config.suggested_bounds()\n     fixed_params = fixed_params or pdf.config.suggested_fixed()\n \n+    _validate_fit_inputs(init_pars, par_bounds, fixed_params)\n+\n     # get fixed vals from the model\n     fixed_vals = [\n         (index, init)\n", "issue": "Parameter bounds affect fits with fixed parameters\n# Description\r\n\r\nSee this example:\r\n```python\r\nimport pyhf\r\n\r\nspec = {\r\n    \"channels\": [\r\n        {\r\n            \"name\": \"SR\",\r\n            \"samples\": [\r\n                {\r\n                    \"data\": [10.0],\r\n                    \"modifiers\": [\r\n                        {\"data\": None, \"name\": \"Signal_norm\", \"type\": \"normfactor\"}\r\n                    ],\r\n                    \"name\": \"Signal\",\r\n                },\r\n                {\"data\": [50.0], \"modifiers\": [], \"name\": \"Background\"},\r\n            ],\r\n        }\r\n    ],\r\n    \"measurements\": [\r\n        {\r\n            \"config\": {\r\n                \"parameters\": [\r\n                    {\"bounds\": [[0, 5]], \"inits\": [2.0], \"name\": \"Signal_norm\"}\r\n                ],\r\n                \"poi\": \"Signal_norm\",\r\n            },\r\n            \"name\": \"minimal_example\",\r\n        }\r\n    ],\r\n    \"observations\": [{\"data\": [65.0], \"name\": \"SR\"}],\r\n    \"version\": \"1.0.0\",\r\n}\r\n\r\nws = pyhf.Workspace(spec)\r\nmodel = ws.model()\r\ndata = ws.data(model)\r\n\r\n# pyhf.infer.hypotest(6.0, data, model, qtilde=True)  # this fails\r\n\r\npyhf.set_backend(\"numpy\", pyhf.optimize.minuit_optimizer(verbose=False))\r\n# the next two work, but result in identical values\r\nprint(pyhf.infer.hypotest(6.0, data, model, qtilde=True))\r\nprint(pyhf.infer.hypotest(7.0, data, model, qtilde=True))\r\n```\r\n\r\nThe CLs value reported in the last two lines is identical. This seems to be related to the parameter bounds `[0,5]`. When increasing the range, the CLs values are different (as expected).\r\n\r\nWhen uncommenting the first `hypotest` (using `scipy`), the minimization fails:\r\n```\r\npyhf.exceptions.FailedMinimization: Positive directional derivative for linesearch\r\n```\r\n\r\n# Expected Behavior\r\n\r\nAll fits succeed, and different CLs for any POI value. If the bounds are somehow still respected, a warning about parameters being outside the bounds (and scaled back to the bound) would be helpful.\r\n\r\n# Actual Behavior\r\n\r\nSame CLs for POI values above bound, and `scipy` backend crashes.\r\n\r\n# Steps to Reproduce\r\n\r\nSee above, same behavior with 0.5.2 and master.\r\n\r\n# Checklist\r\n\r\n- [X] Run `git fetch` to get the most up to date version of `master`\r\n- [X] Searched through existing Issues to confirm this is not a duplicate issue\r\n- [X] Filled out the Description, Expected Behavior, Actual Behavior, and Steps to Reproduce sections above or have edited/removed them in a way that fully describes the issue\r\n\n", "before_files": [{"content": "\"\"\"Module for Maximum Likelihood Estimation.\"\"\"\nfrom .. import get_backend\nfrom ..exceptions import UnspecifiedPOI\n\n\ndef twice_nll(pars, data, pdf):\n    r\"\"\"\n    Two times the negative log-likelihood of the model parameters, :math:`\\left(\\mu, \\boldsymbol{\\theta}\\right)`, given the observed data.\n    It is used in the calculation of the test statistic, :math:`t_{\\mu}`, as defiend in Equation (8) in :xref:`arXiv:1007.1727`\n\n    .. math::\n\n       t_{\\mu} = -2\\ln\\lambda\\left(\\mu\\right)\n\n    where :math:`\\lambda\\left(\\mu\\right)` is the profile likelihood ratio as defined in Equation (7)\n\n    .. math::\n\n       \\lambda\\left(\\mu\\right) = \\frac{L\\left(\\mu, \\hat{\\hat{\\boldsymbol{\\theta}}}\\right)}{L\\left(\\hat{\\mu}, \\hat{\\boldsymbol{\\theta}}\\right)}\\,.\n\n    It serves as the objective function to minimize in :func:`~pyhf.infer.mle.fit`\n    and :func:`~pyhf.infer.mle.fixed_poi_fit`.\n\n    Example:\n        >>> import pyhf\n        >>> pyhf.set_backend(\"numpy\")\n        >>> model = pyhf.simplemodels.hepdata_like(\n        ...     signal_data=[12.0, 11.0], bkg_data=[50.0, 52.0], bkg_uncerts=[3.0, 7.0]\n        ... )\n        >>> observations = [51, 48]\n        >>> data = pyhf.tensorlib.astensor(observations + model.config.auxdata)\n        >>> parameters = model.config.suggested_init()  # nominal parameters\n        >>> twice_nll = pyhf.infer.mle.twice_nll(parameters, data, model)\n        >>> twice_nll\n        array([30.77525435])\n        >>> -2 * model.logpdf(parameters, data) == twice_nll\n        array([ True])\n\n    Args:\n        pars (:obj:`tensor`): The parameters of the HistFactory model\n        data (:obj:`tensor`): The data to be considered\n        pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json\n\n    Returns:\n        Tensor: Two times the negative log-likelihood, :math:`-2\\ln L\\left(\\mu, \\boldsymbol{\\theta}\\right)`\n    \"\"\"\n    return -2 * pdf.logpdf(pars, data)\n\n\ndef fit(data, pdf, init_pars=None, par_bounds=None, fixed_params=None, **kwargs):\n    r\"\"\"\n    Run a maximum likelihood fit.\n    This is done by minimizing the objective function :func:`~pyhf.infer.mle.twice_nll`\n    of the model parameters given the observed data.\n    This is used to produce the maximal likelihood :math:`L\\left(\\hat{\\mu}, \\hat{\\boldsymbol{\\theta}}\\right)`\n    in the profile likelihood ratio in Equation (7) in :xref:`arXiv:1007.1727`\n\n    .. math::\n\n       \\lambda\\left(\\mu\\right) = \\frac{L\\left(\\mu, \\hat{\\hat{\\boldsymbol{\\theta}}}\\right)}{L\\left(\\hat{\\mu}, \\hat{\\boldsymbol{\\theta}}\\right)}\n\n\n    .. note::\n\n        :func:`twice_nll` is the objective function given to the optimizer and\n        is returned evaluated at the best fit model parameters when the optional\n        kwarg ``return_fitted_val`` is ``True``.\n\n    Example:\n        >>> import pyhf\n        >>> pyhf.set_backend(\"numpy\")\n        >>> model = pyhf.simplemodels.hepdata_like(\n        ...     signal_data=[12.0, 11.0], bkg_data=[50.0, 52.0], bkg_uncerts=[3.0, 7.0]\n        ... )\n        >>> observations = [51, 48]\n        >>> data = pyhf.tensorlib.astensor(observations + model.config.auxdata)\n        >>> bestfit_pars, twice_nll = pyhf.infer.mle.fit(data, model, return_fitted_val=True)\n        >>> bestfit_pars\n        array([0.        , 1.0030512 , 0.96266961])\n        >>> twice_nll\n        array(24.98393521)\n        >>> -2 * model.logpdf(bestfit_pars, data) == twice_nll\n        array([ True])\n\n    Args:\n        data (:obj:`tensor`): The data\n        pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json\n        init_pars (:obj:`list`): Values to initialize the model parameters at for the fit\n        par_bounds (:obj:`list` of :obj:`list`\\s or :obj:`tuple`\\s): The extrema of values the model parameters are allowed to reach in the fit\n        fixed_params (:obj:`list`): Parameters to be held constant in the fit.\n        kwargs: Keyword arguments passed through to the optimizer API\n\n    Returns:\n        See optimizer API\n\n    \"\"\"\n    _, opt = get_backend()\n    init_pars = init_pars or pdf.config.suggested_init()\n    par_bounds = par_bounds or pdf.config.suggested_bounds()\n    fixed_params = fixed_params or pdf.config.suggested_fixed()\n\n    # get fixed vals from the model\n    fixed_vals = [\n        (index, init)\n        for index, (init, is_fixed) in enumerate(zip(init_pars, fixed_params))\n        if is_fixed\n    ]\n\n    return opt.minimize(\n        twice_nll, data, pdf, init_pars, par_bounds, fixed_vals, **kwargs\n    )\n\n\ndef fixed_poi_fit(\n    poi_val, data, pdf, init_pars=None, par_bounds=None, fixed_params=None, **kwargs\n):\n    r\"\"\"\n    Run a maximum likelihood fit with the POI value fixed.\n    This is done by minimizing the objective function of :func:`~pyhf.infer.mle.twice_nll`\n    of the model parameters given the observed data, for a given fixed value of :math:`\\mu`.\n    This is used to produce the constrained maximal likelihood for the given :math:`\\mu`\n    :math:`L\\left(\\mu, \\hat{\\hat{\\boldsymbol{\\theta}}}\\right)` in the profile\n    likelihood ratio in Equation (7) in :xref:`arXiv:1007.1727`\n\n    .. math::\n\n       \\lambda\\left(\\mu\\right) = \\frac{L\\left(\\mu, \\hat{\\hat{\\boldsymbol{\\theta}}}\\right)}{L\\left(\\hat{\\mu}, \\hat{\\boldsymbol{\\theta}}\\right)}\n\n    .. note::\n\n        :func:`twice_nll` is the objective function given to the optimizer and\n        is returned evaluated at the best fit model parameters when the optional\n        kwarg ``return_fitted_val`` is ``True``.\n\n    Example:\n        >>> import pyhf\n        >>> pyhf.set_backend(\"numpy\")\n        >>> model = pyhf.simplemodels.hepdata_like(\n        ...     signal_data=[12.0, 11.0], bkg_data=[50.0, 52.0], bkg_uncerts=[3.0, 7.0]\n        ... )\n        >>> observations = [51, 48]\n        >>> data = pyhf.tensorlib.astensor(observations + model.config.auxdata)\n        >>> test_poi = 1.0\n        >>> bestfit_pars, twice_nll = pyhf.infer.mle.fixed_poi_fit(\n        ...     test_poi, data, model, return_fitted_val=True\n        ... )\n        >>> bestfit_pars\n        array([1.        , 0.97224597, 0.87553894])\n        >>> twice_nll\n        array(28.92218013)\n        >>> -2 * model.logpdf(bestfit_pars, data) == twice_nll\n        array([ True])\n\n    Args:\n        data: The data\n        pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json\n        init_pars (:obj:`list`): Values to initialize the model parameters at for the fit\n        par_bounds (:obj:`list` of :obj:`list`\\s or :obj:`tuple`\\s): The extrema of values the model parameters are allowed to reach in the fit\n        fixed_params (:obj:`list`): Parameters to be held constant in the fit.\n        kwargs: Keyword arguments passed through to the optimizer API\n\n    Returns:\n        See optimizer API\n\n    \"\"\"\n    if pdf.config.poi_index is None:\n        raise UnspecifiedPOI(\n            'No POI is defined. A POI is required to fit with a fixed POI.'\n        )\n\n    init_pars = [*(init_pars or pdf.config.suggested_init())]\n    fixed_params = [*(fixed_params or pdf.config.suggested_fixed())]\n\n    init_pars[pdf.config.poi_index] = poi_val\n    fixed_params[pdf.config.poi_index] = True\n\n    return fit(data, pdf, init_pars, par_bounds, fixed_params, **kwargs)\n", "path": "src/pyhf/infer/mle.py"}], "after_files": [{"content": "\"\"\"Module for Maximum Likelihood Estimation.\"\"\"\nfrom .. import get_backend\nfrom ..exceptions import UnspecifiedPOI\n\n\ndef twice_nll(pars, data, pdf):\n    r\"\"\"\n    Two times the negative log-likelihood of the model parameters, :math:`\\left(\\mu, \\boldsymbol{\\theta}\\right)`, given the observed data.\n    It is used in the calculation of the test statistic, :math:`t_{\\mu}`, as defiend in Equation (8) in :xref:`arXiv:1007.1727`\n\n    .. math::\n\n       t_{\\mu} = -2\\ln\\lambda\\left(\\mu\\right)\n\n    where :math:`\\lambda\\left(\\mu\\right)` is the profile likelihood ratio as defined in Equation (7)\n\n    .. math::\n\n       \\lambda\\left(\\mu\\right) = \\frac{L\\left(\\mu, \\hat{\\hat{\\boldsymbol{\\theta}}}\\right)}{L\\left(\\hat{\\mu}, \\hat{\\boldsymbol{\\theta}}\\right)}\\,.\n\n    It serves as the objective function to minimize in :func:`~pyhf.infer.mle.fit`\n    and :func:`~pyhf.infer.mle.fixed_poi_fit`.\n\n    Example:\n        >>> import pyhf\n        >>> pyhf.set_backend(\"numpy\")\n        >>> model = pyhf.simplemodels.hepdata_like(\n        ...     signal_data=[12.0, 11.0], bkg_data=[50.0, 52.0], bkg_uncerts=[3.0, 7.0]\n        ... )\n        >>> observations = [51, 48]\n        >>> data = pyhf.tensorlib.astensor(observations + model.config.auxdata)\n        >>> parameters = model.config.suggested_init()  # nominal parameters\n        >>> twice_nll = pyhf.infer.mle.twice_nll(parameters, data, model)\n        >>> twice_nll\n        array([30.77525435])\n        >>> -2 * model.logpdf(parameters, data) == twice_nll\n        array([ True])\n\n    Args:\n        pars (:obj:`tensor`): The parameters of the HistFactory model\n        data (:obj:`tensor`): The data to be considered\n        pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json\n\n    Returns:\n        Tensor: Two times the negative log-likelihood, :math:`-2\\ln L\\left(\\mu, \\boldsymbol{\\theta}\\right)`\n    \"\"\"\n    return -2 * pdf.logpdf(pars, data)\n\n\ndef _validate_fit_inputs(init_pars, par_bounds, fixed_params):\n    for par_idx, (value, bound) in enumerate(zip(init_pars, par_bounds)):\n        if not (bound[0] <= value <= bound[1]):\n            raise ValueError(\n                f\"fit initialization parameter (index: {par_idx}, value: {value}) lies outside of its bounds: {bound}\"\n                + \"\\nTo correct this adjust the initialization parameter values in the model spec or those given\"\n                + \"\\nas arguments to pyhf.infer.fit. If this value is intended, adjust the range of the parameter\"\n                + \"\\nbounds.\"\n            )\n\n\ndef fit(data, pdf, init_pars=None, par_bounds=None, fixed_params=None, **kwargs):\n    r\"\"\"\n    Run a maximum likelihood fit.\n    This is done by minimizing the objective function :func:`~pyhf.infer.mle.twice_nll`\n    of the model parameters given the observed data.\n    This is used to produce the maximal likelihood :math:`L\\left(\\hat{\\mu}, \\hat{\\boldsymbol{\\theta}}\\right)`\n    in the profile likelihood ratio in Equation (7) in :xref:`arXiv:1007.1727`\n\n    .. math::\n\n       \\lambda\\left(\\mu\\right) = \\frac{L\\left(\\mu, \\hat{\\hat{\\boldsymbol{\\theta}}}\\right)}{L\\left(\\hat{\\mu}, \\hat{\\boldsymbol{\\theta}}\\right)}\n\n\n    .. note::\n\n        :func:`twice_nll` is the objective function given to the optimizer and\n        is returned evaluated at the best fit model parameters when the optional\n        kwarg ``return_fitted_val`` is ``True``.\n\n    Example:\n        >>> import pyhf\n        >>> pyhf.set_backend(\"numpy\")\n        >>> model = pyhf.simplemodels.hepdata_like(\n        ...     signal_data=[12.0, 11.0], bkg_data=[50.0, 52.0], bkg_uncerts=[3.0, 7.0]\n        ... )\n        >>> observations = [51, 48]\n        >>> data = pyhf.tensorlib.astensor(observations + model.config.auxdata)\n        >>> bestfit_pars, twice_nll = pyhf.infer.mle.fit(data, model, return_fitted_val=True)\n        >>> bestfit_pars\n        array([0.        , 1.0030512 , 0.96266961])\n        >>> twice_nll\n        array(24.98393521)\n        >>> -2 * model.logpdf(bestfit_pars, data) == twice_nll\n        array([ True])\n\n    Args:\n        data (:obj:`tensor`): The data\n        pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json\n        init_pars (:obj:`list`): Values to initialize the model parameters at for the fit\n        par_bounds (:obj:`list` of :obj:`list`\\s or :obj:`tuple`\\s): The extrema of values the model parameters are allowed to reach in the fit\n        fixed_params (:obj:`list`): Parameters to be held constant in the fit.\n        kwargs: Keyword arguments passed through to the optimizer API\n\n    Returns:\n        See optimizer API\n\n    \"\"\"\n    _, opt = get_backend()\n    init_pars = init_pars or pdf.config.suggested_init()\n    par_bounds = par_bounds or pdf.config.suggested_bounds()\n    fixed_params = fixed_params or pdf.config.suggested_fixed()\n\n    _validate_fit_inputs(init_pars, par_bounds, fixed_params)\n\n    # get fixed vals from the model\n    fixed_vals = [\n        (index, init)\n        for index, (init, is_fixed) in enumerate(zip(init_pars, fixed_params))\n        if is_fixed\n    ]\n\n    return opt.minimize(\n        twice_nll, data, pdf, init_pars, par_bounds, fixed_vals, **kwargs\n    )\n\n\ndef fixed_poi_fit(\n    poi_val, data, pdf, init_pars=None, par_bounds=None, fixed_params=None, **kwargs\n):\n    r\"\"\"\n    Run a maximum likelihood fit with the POI value fixed.\n    This is done by minimizing the objective function of :func:`~pyhf.infer.mle.twice_nll`\n    of the model parameters given the observed data, for a given fixed value of :math:`\\mu`.\n    This is used to produce the constrained maximal likelihood for the given :math:`\\mu`\n    :math:`L\\left(\\mu, \\hat{\\hat{\\boldsymbol{\\theta}}}\\right)` in the profile\n    likelihood ratio in Equation (7) in :xref:`arXiv:1007.1727`\n\n    .. math::\n\n       \\lambda\\left(\\mu\\right) = \\frac{L\\left(\\mu, \\hat{\\hat{\\boldsymbol{\\theta}}}\\right)}{L\\left(\\hat{\\mu}, \\hat{\\boldsymbol{\\theta}}\\right)}\n\n    .. note::\n\n        :func:`twice_nll` is the objective function given to the optimizer and\n        is returned evaluated at the best fit model parameters when the optional\n        kwarg ``return_fitted_val`` is ``True``.\n\n    Example:\n        >>> import pyhf\n        >>> pyhf.set_backend(\"numpy\")\n        >>> model = pyhf.simplemodels.hepdata_like(\n        ...     signal_data=[12.0, 11.0], bkg_data=[50.0, 52.0], bkg_uncerts=[3.0, 7.0]\n        ... )\n        >>> observations = [51, 48]\n        >>> data = pyhf.tensorlib.astensor(observations + model.config.auxdata)\n        >>> test_poi = 1.0\n        >>> bestfit_pars, twice_nll = pyhf.infer.mle.fixed_poi_fit(\n        ...     test_poi, data, model, return_fitted_val=True\n        ... )\n        >>> bestfit_pars\n        array([1.        , 0.97224597, 0.87553894])\n        >>> twice_nll\n        array(28.92218013)\n        >>> -2 * model.logpdf(bestfit_pars, data) == twice_nll\n        array([ True])\n\n    Args:\n        data: The data\n        pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json\n        init_pars (:obj:`list`): Values to initialize the model parameters at for the fit\n        par_bounds (:obj:`list` of :obj:`list`\\s or :obj:`tuple`\\s): The extrema of values the model parameters are allowed to reach in the fit\n        fixed_params (:obj:`list`): Parameters to be held constant in the fit.\n        kwargs: Keyword arguments passed through to the optimizer API\n\n    Returns:\n        See optimizer API\n\n    \"\"\"\n    if pdf.config.poi_index is None:\n        raise UnspecifiedPOI(\n            'No POI is defined. A POI is required to fit with a fixed POI.'\n        )\n\n    init_pars = [*(init_pars or pdf.config.suggested_init())]\n    fixed_params = [*(fixed_params or pdf.config.suggested_fixed())]\n\n    init_pars[pdf.config.poi_index] = poi_val\n    fixed_params[pdf.config.poi_index] = True\n\n    return fit(data, pdf, init_pars, par_bounds, fixed_params, **kwargs)\n", "path": "src/pyhf/infer/mle.py"}]}