Skip to content

Commit 4ef9bfc

Browse files
authored
feat: Bookkeep fixed parameters (#989)
* Add functionality into _ModelConfig to bookkeep fixed systematics (all parameters for a given systematic) * Add fixed into tests for paramsets * Add tests for fixed parameters
1 parent 5fcf95b commit 4ef9bfc

15 files changed

+249
-27
lines changed

src/pyhf/modifiers/histosys.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def required_parset(cls, sample_data, modifier_data):
2020
'is_shared': True,
2121
'inits': (0.0,),
2222
'bounds': ((-5.0, 5.0),),
23+
'fixed': False,
2324
'auxdata': (0.0,),
2425
}
2526

src/pyhf/modifiers/lumi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def required_parset(cls, sample_data, modifier_data):
2020
'op_code': cls.op_code,
2121
'inits': None, # lumi
2222
'bounds': None, # (0, 10*lumi)
23+
'fixed': False,
2324
'auxdata': None, # lumi
2425
'sigmas': None, # lumi * lumirelerror
2526
}

src/pyhf/modifiers/normfactor.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def required_parset(cls, sample_data, modifier_data):
1919
'is_shared': True,
2020
'inits': (1.0,),
2121
'bounds': ((0, 10),),
22+
'fixed': False,
2223
}
2324

2425

src/pyhf/modifiers/normsys.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def required_parset(cls, sample_data, modifier_data):
2020
'is_shared': True,
2121
'inits': (0.0,),
2222
'bounds': ((-5.0, 5.0),),
23+
'fixed': False,
2324
'auxdata': (0.0,),
2425
}
2526

src/pyhf/modifiers/shapefactor.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def required_parset(cls, sample_data, modifier_data):
1919
'is_shared': True,
2020
'inits': (1.0,) * len(sample_data),
2121
'bounds': ((0.0, 10.0),) * len(sample_data),
22+
'fixed': False,
2223
}
2324

2425

src/pyhf/modifiers/shapesys.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def required_parset(cls, sample_data, modifier_data):
2727
'is_shared': False,
2828
'inits': (1.0,) * n_parameters,
2929
'bounds': ((1e-10, 10.0),) * n_parameters,
30+
'fixed': False,
3031
# nb: auxdata/factors set by finalize. Set to non-numeric to crash
3132
# if we fail to set auxdata/factors correctly
3233
'auxdata': (None,) * n_parameters,

src/pyhf/modifiers/staterror.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def required_parset(cls, sample_data, modifier_data):
1919
'is_shared': True,
2020
'inits': (1.0,) * len(sample_data),
2121
'bounds': ((1e-10, 10.0),) * len(sample_data),
22+
'fixed': False,
2223
'auxdata': (1.0,) * len(sample_data),
2324
}
2425

src/pyhf/parameters/paramsets.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ def __init__(self, **kwargs):
66
self.n_parameters = kwargs.pop('n_parameters')
77
self.suggested_init = kwargs.pop('inits')
88
self.suggested_bounds = kwargs.pop('bounds')
9+
self.fixed = kwargs.pop('fixed')
910

1011

1112
class unconstrained(paramset):

src/pyhf/parameters/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def reduce_paramsets_requirements(paramsets_requirements, paramsets_user_configs
1212
'auxdata',
1313
'factors',
1414
'sigmas',
15+
'fixed',
1516
]
1617

1718
# - process all defined paramsets
@@ -27,6 +28,7 @@ def reduce_paramsets_requirements(paramsets_requirements, paramsets_user_configs
2728
combined_paramset = {}
2829
for k in paramset_keys:
2930
for paramset_requirement in paramset_requirements:
31+
# undefined: the modifier does not support configuring that property
3032
v = paramset_requirement.get(k, 'undefined')
3133
combined_paramset.setdefault(k, set([])).add(v)
3234

src/pyhf/pdf.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def _paramset_requirements_from_modelspec(spec, channel_nbins):
9393
paramset_type = paramset_requirements.get('paramset_type')
9494
paramset = paramset_type(**paramset_requirements)
9595
_sets[param_name] = paramset
96+
9697
return _sets
9798

9899

@@ -229,8 +230,8 @@ def __init__(self, spec, **config_kwargs):
229230
)
230231

231232
if config_kwargs:
232-
raise KeyError(
233-
f"""Unexpected keyword argument(s): '{"', '".join(config_kwargs.keys())}'"""
233+
raise exceptions.Unsupported(
234+
f"Unsupported options were passed in: {list(config_kwargs.keys())}."
234235
)
235236

236237
self.par_map = {}
@@ -265,6 +266,31 @@ def par_slice(self, name):
265266
def param_set(self, name):
266267
return self.par_map[name]['paramset']
267268

269+
def suggested_fixed(self):
270+
"""
271+
Identify the fixed parameters in the model.
272+
273+
Returns:
274+
List: A list of booleans, ``True`` for fixed and ``False`` for not fixed.
275+
276+
Something like the following to build fixed_vals appropriately:
277+
278+
.. code:: python
279+
280+
fixed_pars = pdf.config.suggested_fixed()
281+
inits = pdf.config.suggested_init()
282+
fixed_vals = [
283+
(index, init)
284+
for index, (init, is_fixed) in enumerate(zip(inits, fixed_pars))
285+
if is_fixed
286+
]
287+
"""
288+
fixed = []
289+
for name in self.par_order:
290+
paramset = self.par_map[name]['paramset']
291+
fixed = fixed + [paramset.fixed] * paramset.n_parameters
292+
return fixed
293+
268294
def set_poi(self, name):
269295
if name not in [x for x, _ in self.modifiers]:
270296
raise exceptions.InvalidModel(

tests/test_combined_modifiers.py

Lines changed: 107 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
from pyhf.modifiers.shapesys import shapesys_combined
66
from pyhf.modifiers.normfactor import normfactor_combined
77
from pyhf.modifiers.shapefactor import shapefactor_combined
8-
from pyhf.parameters import paramset
8+
from pyhf.parameters import (
9+
paramset,
10+
unconstrained,
11+
constrained_by_normal,
12+
constrained_by_poisson,
13+
)
914
import numpy as np
1015
import pyhf
1116

@@ -42,11 +47,23 @@ def test_histosys(backend):
4247
mc = MockConfig(
4348
par_map={
4449
'hello': {
45-
'paramset': paramset(n_parameters=1, inits=[0], bounds=[[-5, 5]]),
50+
'paramset': constrained_by_normal(
51+
n_parameters=1,
52+
inits=[0],
53+
bounds=[[-5, 5]],
54+
fixed=False,
55+
auxdata=[0.0],
56+
),
4657
'slice': slice(0, 1),
4758
},
4859
'world': {
49-
'paramset': paramset(n_parameters=1, inits=[0], bounds=[[-5, 5]]),
60+
'paramset': constrained_by_normal(
61+
n_parameters=1,
62+
inits=[0],
63+
bounds=[[-5, 5]],
64+
fixed=False,
65+
auxdata=[0.0],
66+
),
5067
'slice': slice(1, 2),
5168
},
5269
},
@@ -131,11 +148,23 @@ def test_normsys(backend):
131148
mc = MockConfig(
132149
par_map={
133150
'hello': {
134-
'paramset': paramset(n_parameters=1, inits=[0], bounds=[[-5, 5]]),
151+
'paramset': constrained_by_normal(
152+
n_parameters=1,
153+
inits=[0],
154+
bounds=[[-5, 5]],
155+
fixed=False,
156+
auxdata=[0.0],
157+
),
135158
'slice': slice(0, 1),
136159
},
137160
'world': {
138-
'paramset': paramset(n_parameters=1, inits=[0], bounds=[[-5, 5]]),
161+
'paramset': constrained_by_normal(
162+
n_parameters=1,
163+
inits=[0],
164+
bounds=[[-5, 5]],
165+
fixed=False,
166+
auxdata=[0.0],
167+
),
139168
'slice': slice(1, 2),
140169
},
141170
},
@@ -222,7 +251,14 @@ def test_lumi(backend):
222251
mc = MockConfig(
223252
par_map={
224253
'lumi': {
225-
'paramset': paramset(n_parameters=1, inits=[0], bounds=[[-5, 5]]),
254+
'paramset': constrained_by_normal(
255+
n_parameters=1,
256+
inits=[0],
257+
bounds=[[-5, 5]],
258+
fixed=False,
259+
auxdata=[None],
260+
sigmas=[None],
261+
),
226262
'slice': slice(0, 1),
227263
}
228264
},
@@ -272,12 +308,22 @@ def test_stat(backend):
272308
mc = MockConfig(
273309
par_map={
274310
'staterror_chan1': {
275-
'paramset': paramset(n_parameters=1, inits=[1], bounds=[[0, 10]]),
311+
'paramset': constrained_by_normal(
312+
n_parameters=1,
313+
inits=[1],
314+
bounds=[[0, 10]],
315+
fixed=False,
316+
auxdata=[1],
317+
),
276318
'slice': slice(0, 1),
277319
},
278320
'staterror_chan2': {
279-
'paramset': paramset(
280-
n_parameters=2, inits=[1, 1], bounds=[[0, 10], [0, 10]]
321+
'paramset': constrained_by_normal(
322+
n_parameters=2,
323+
inits=[1, 1],
324+
bounds=[[0, 10], [0, 10]],
325+
fixed=False,
326+
auxdata=[1, 1],
281327
),
282328
'slice': slice(1, 3),
283329
},
@@ -349,21 +395,37 @@ def test_shapesys(backend):
349395
mc = MockConfig(
350396
par_map={
351397
'dummy1': {
352-
'paramset': paramset(n_parameters=1, inits=[0], bounds=[[0, 10]]),
398+
'paramset': paramset(
399+
n_parameters=1, inits=[0], bounds=[[0, 10]], fixed=False,
400+
),
353401
'slice': slice(0, 1),
354402
},
355403
'shapesys1': {
356-
'paramset': paramset(n_parameters=1, inits=[0], bounds=[[0, 10]]),
404+
'paramset': constrained_by_poisson(
405+
n_parameters=1,
406+
inits=[0],
407+
bounds=[[0, 10]],
408+
fixed=False,
409+
auxdata=[None],
410+
factors=[None],
411+
),
357412
'slice': slice(1, 2),
358413
},
359414
'shapesys2': {
360-
'paramset': paramset(
361-
n_parameters=2, inits=[0, 0], bounds=[[0, 10], [0, 10]]
415+
'paramset': constrained_by_poisson(
416+
n_parameters=2,
417+
inits=[0, 0],
418+
bounds=[[0, 10], [0, 10]],
419+
fixed=False,
420+
auxdata=[None, None],
421+
factors=[None, None],
362422
),
363423
'slice': slice(2, 4),
364424
},
365425
'dummy2': {
366-
'paramset': paramset(n_parameters=1, inits=[0], bounds=[[0, 10]]),
426+
'paramset': paramset(
427+
n_parameters=1, inits=[0], bounds=[[0, 10]], fixed=False,
428+
),
367429
'slice': slice(4, 5),
368430
},
369431
},
@@ -432,11 +494,15 @@ def test_normfactor(backend):
432494
mc = MockConfig(
433495
par_map={
434496
'mu1': {
435-
'paramset': paramset(n_parameters=1, inits=[0], bounds=[[0, 10]]),
497+
'paramset': unconstrained(
498+
n_parameters=1, inits=[0], bounds=[[0, 10]], fixed=False,
499+
),
436500
'slice': slice(0, 1),
437501
},
438502
'mu2': {
439-
'paramset': paramset(n_parameters=1, inits=[0], bounds=[[0, 10]]),
503+
'paramset': unconstrained(
504+
n_parameters=1, inits=[0], bounds=[[0, 10]], fixed=False,
505+
),
440506
'slice': slice(1, 2),
441507
},
442508
},
@@ -508,18 +574,30 @@ def test_shapesys_zero(backend):
508574
mc = MockConfig(
509575
par_map={
510576
'SigXsecOverSM': {
511-
'paramset': paramset(n_parameters=1, inits=[0], bounds=[[0, 10]]),
577+
'paramset': paramset(
578+
n_parameters=1, inits=[0], bounds=[[0, 10]], fixed=False,
579+
),
512580
'slice': slice(0, 1),
513581
},
514582
'syst': {
515-
'paramset': paramset(
516-
n_parameters=5, inits=[0] * 5, bounds=[[0, 10]] * 5
583+
'paramset': constrained_by_poisson(
584+
n_parameters=5,
585+
inits=[0] * 5,
586+
bounds=[[0, 10]] * 5,
587+
fixed=False,
588+
auxdata=[None] * 5,
589+
factors=[None] * 5,
517590
),
518591
'slice': slice(1, 6),
519592
},
520593
'syst_lowstats': {
521-
'paramset': paramset(
522-
n_parameters=0, inits=[0] * 0, bounds=[[0, 10]] * 0
594+
'paramset': constrained_by_poisson(
595+
n_parameters=0,
596+
inits=[0] * 0,
597+
bounds=[[0, 10]] * 0,
598+
fixed=False,
599+
auxdata=[None] * 0,
600+
factors=[None] * 0,
523601
),
524602
'slice': slice(6, 6),
525603
},
@@ -590,12 +668,17 @@ def test_shapefactor(backend):
590668
mc = MockConfig(
591669
par_map={
592670
'shapefac1': {
593-
'paramset': paramset(n_parameters=1, inits=[0], bounds=[[0, 10]]),
671+
'paramset': unconstrained(
672+
n_parameters=1, inits=[0], bounds=[[0, 10]], fixed=False,
673+
),
594674
'slice': slice(0, 1),
595675
},
596676
'shapefac2': {
597-
'paramset': paramset(
598-
n_parameters=2, inits=[0, 0], bounds=[[0, 10], [0, 10]]
677+
'paramset': unconstrained(
678+
n_parameters=2,
679+
inits=[0, 0],
680+
bounds=[[0, 10], [0, 10]],
681+
fixed=False,
599682
),
600683
'slice': slice(1, 3),
601684
},

tests/test_constraints.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ def test_batched_constraints(backend):
141141
bounds=[[0, 10]],
142142
auxdata=[12],
143143
factors=[12],
144+
fixed=False,
144145
),
145146
'slice': slice(0, 1),
146147
'auxdata': [1],
@@ -152,6 +153,7 @@ def test_batched_constraints(backend):
152153
bounds=[[0, 10]] * 2,
153154
auxdata=[13, 14],
154155
factors=[13, 14],
156+
fixed=False,
155157
),
156158
'slice': slice(1, 3),
157159
},
@@ -162,6 +164,7 @@ def test_batched_constraints(backend):
162164
bounds=[[0, 10]] * 2,
163165
auxdata=[0, 0],
164166
sigmas=[1.5, 2.0],
167+
fixed=False,
165168
),
166169
'slice': slice(3, 5),
167170
},
@@ -171,6 +174,7 @@ def test_batched_constraints(backend):
171174
inits=[0] * 3,
172175
bounds=[[0, 10]] * 3,
173176
auxdata=[0, 0, 0],
177+
fixed=False,
174178
),
175179
'slice': slice(5, 8),
176180
},

0 commit comments

Comments
 (0)