Repository: scikit-hep/pyhf

Issue:
`shapefactor` as parameter of interest
### Discussed in https://github.com/scikit-hep/pyhf/discussions/2194

<div type='discussions-op-text'>

<sup>Originally posted by **TommyDESY** May  4, 2023</sup>
Hi all,

I'm currently setting up a fit with 5 templates/samples. I included as a PoI a `normfactor` (Unconstrained Normalisation) which didn't cause any particular issue. I managed to create my pyhf model and managed to plot pulls, errors and fit with Asimov data. 

However, what I actually want to do is use a `shapefactor` (Data-driven Shape). I use exactly the same code as previously but here an error arises. After building my pyhf spec I add the `shapefactor` as a modifier:

`sample['modifiers'].append({'name': 'mu', 'type': 'shapefactor', 'data': None})`

When I build my model with `model = pyhf.Model(pyhf_spec)`, I get an AssertionError from pyhf/pdf.py l. 468:

`    467         s = self.par_slice(name)`
`--> 468         assert s.stop - s.start == 1`

With a `normfactor`, this slice is indeed 1, but with `shapefactor` this slice has the same size as the number of bins in the fit (17 bins in lepton energy in my specific case).

I tried to remove this line from pdf.py but then this causes other errors when running the MLE fit. Therefore, it seems that this requirement is important. 

What am I missing exactly here ? Is there anything specific that needs to be added to the pyhf spec when considering data-driven shapes ? Or is this due to some know issue ?

Thank you for your help.</div>


Discussion:

Resolving patch:
diff --git a/src/pyhf/pdf.py b/src/pyhf/pdf.py
--- a/src/pyhf/pdf.py
+++ b/src/pyhf/pdf.py
@@ -464,10 +464,13 @@ def set_poi(self, name):
             raise exceptions.InvalidModel(
                 f"The parameter of interest '{name:s}' cannot be fit as it is not declared in the model specification."
             )
-        s = self.par_slice(name)
-        assert s.stop - s.start == 1
+        if self.param_set(name).n_parameters > 1:
+            # multi-parameter modifiers are not supported as POIs
+            raise exceptions.InvalidModel(
+                f"The parameter '{name:s}' contains multiple components and is not currently supported as parameter of interest."
+            )
         self._poi_name = name
-        self._poi_index = s.start
+        self._poi_index = self.par_slice(name).start
 
     def _create_and_register_paramsets(self, required_paramsets):
         next_index = 0


Test patch:
diff --git a/tests/test_pdf.py b/tests/test_pdf.py
--- a/tests/test_pdf.py
+++ b/tests/test_pdf.py
@@ -1329,3 +1329,33 @@ def test_is_shared_paramset_shapesys_same_sample_same_channel():
 
     with pytest.raises(pyhf.exceptions.InvalidModel):
         pyhf.Workspace(spec).model()
+
+
+def test_multi_component_poi():
+    spec = {
+        "channels": [
+            {
+                "name": "SR",
+                "samples": [
+                    {
+                        "data": [5.0, 10.0],
+                        "modifiers": [
+                            {"data": None, "name": "mu", "type": "shapefactor"}
+                        ],
+                        "name": "Signal",
+                    }
+                ],
+            }
+        ],
+        "measurements": [
+            {"config": {"parameters": [], "poi": "mu"}, "name": "example"}
+        ],
+        "observations": [{"data": [5.0, 10.0], "name": "SR"}],
+        "version": "1.0.0",
+    }
+
+    with pytest.raises(
+        pyhf.exceptions.InvalidModel,
+        match="The parameter 'mu' contains multiple components and is not currently supported as parameter of interest.",
+    ):
+        pyhf.Workspace(spec).model()