Repository: scikit-hep/pyhf

Issue:
workspaces with fixed poi fail in hypotest (of course)
# Description

some workspaces (like the one @elskorda provided) have a *fixed* POI in the workspace definition - if you naively put that through `pyhf cls` of course the fit doesn't really do anythinig useful.

options

* in readxml warn that POI is fixed
* in hypotest ensurer to un-fix POI
* others?


the least we could do is throw a warning  in the places where an unconstrained fit iss expected but a fixed POI is observed


Discussion:
`hypotest` seems like a good place for a warning, maybe even error. With the POI fixed, the result cannot really be meaningful. As a step towards multi-POI support, it might also be useful to have an option to override the POI from the CLI.

Resolving patch:
diff --git a/src/pyhf/infer/__init__.py b/src/pyhf/infer/__init__.py
--- a/src/pyhf/infer/__init__.py
+++ b/src/pyhf/infer/__init__.py
@@ -2,6 +2,19 @@
 
 from . import utils
 from .. import get_backend
+from .. import exceptions
+
+
+def _check_hypotest_prerequisites(pdf, data, init_pars, par_bounds, fixed_params):
+    if pdf.config.poi_index is None:
+        raise exceptions.UnspecifiedPOI(
+            'No POI is defined. A POI is required to run a hypothesis test.'
+        )
+
+    if not utils.all_pois_floating(pdf, fixed_params):
+        raise exceptions.InvalidModel(
+            f'POI at index [{pdf.config.poi_index}] is set as fixed, which makes inference impossible. Please unfix the POI to continue.'
+        )
 
 
 def hypotest(
@@ -131,6 +144,8 @@ def hypotest(
     par_bounds = par_bounds or pdf.config.suggested_bounds()
     fixed_params = fixed_params or pdf.config.suggested_fixed()
 
+    _check_hypotest_prerequisites(pdf, data, init_pars, par_bounds, fixed_params)
+
     calc = utils.create_calculator(
         calctype,
         data,
diff --git a/src/pyhf/infer/utils.py b/src/pyhf/infer/utils.py
--- a/src/pyhf/infer/utils.py
+++ b/src/pyhf/infer/utils.py
@@ -9,6 +9,24 @@
 log = logging.getLogger(__name__)
 
 
+def all_pois_floating(pdf, fixed_params):
+    r"""
+    Check whether all POI(s) are floating (i.e. not within the fixed set).
+
+    Args:
+        pdf (~pyhf.pdf.Model): The statistical model adhering to the schema
+         ``model.json``.
+        fixed_params (:obj:`list` or `tensor` of :obj:`bool`): Array of
+         :obj:`bool` indicating if model parameters are fixed.
+
+    Returns:
+        :obj:`bool`: The result whether all POIs are floating.
+    """
+
+    poi_fixed = fixed_params[pdf.config.poi_index]
+    return not poi_fixed
+
+
 def create_calculator(calctype, *args, **kwargs):
     """
     Creates a calculator object of the specified `calctype`.


Test patch:
diff --git a/tests/test_infer.py b/tests/test_infer.py
--- a/tests/test_infer.py
+++ b/tests/test_infer.py
@@ -395,3 +395,15 @@ def test_toy_calculator(tmpdir, hypotest_args):
     assert toy_calculator_qtilde_mu.teststatistic(mu_test) == pytest.approx(
         3.938244920380498, 1e-07
     )
+
+
+def test_fixed_poi(tmpdir, hypotest_args):
+    """
+    Check that the return structure of pyhf.infer.hypotest with the
+    additon of the return_expected keyword arg is as expected
+    """
+
+    _, _, pdf = hypotest_args
+    pdf.config.param_set('mu').suggested_fixed = [True]
+    with pytest.raises(pyhf.exceptions.InvalidModel):
+        pyhf.infer.hypotest(*hypotest_args)