Skip to content

Commit 2cabb09

Browse files
kratsgcgottard
andauthored
feat: Support configuring fixed values automatically from Model (#1051)
* Add support for automatically passing through fixed parameters from the model configuration into all of the minimization/inference API * Add tests for fixed values Co-authored-by: Carlo Gottardo <carlo.gottardo91@gmail.com>
1 parent 516ee1b commit 2cabb09

File tree

10 files changed

+163
-57
lines changed

10 files changed

+163
-57
lines changed

docs/examples/notebooks/ImpactPlot.ipynb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,17 @@
8282
" _, model, data = make_model([\"CRtt_meff\"])\n",
8383
"\n",
8484
" pyhf.set_backend(\"numpy\", pyhf.optimize.minuit_optimizer(verbose=True))\n",
85+
" \n",
86+
" constraints = constraints or []\n",
87+
" init_pars = model.config.suggested_init()\n",
88+
" fixed_params = model.config.suggested_fixed()\n",
89+
" \n",
90+
" for idx,fixed_val in constraints:\n",
91+
" init_pars[idx] = fixed_val\n",
92+
" fixed_params[idx] = True\n",
93+
" \n",
8594
" result = pyhf.infer.mle.fit(\n",
86-
" data, model, fixed_vals=constraints, return_uncertainties=True\n",
95+
" data, model, init_pars=init_pars, fixed_params=fixed_params, return_uncertainties=True\n",
8796
" )\n",
8897
" bestfit = result[:, 0]\n",
8998
" errors = result[:, 1]\n",

docs/examples/notebooks/pullplot.ipynb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,17 @@
7272
" _, model, data = make_model([\"CRtt_meff\"])\n",
7373
"\n",
7474
" pyhf.set_backend(\"numpy\", pyhf.optimize.minuit_optimizer(verbose=True))\n",
75+
" \n",
76+
" constraints = constraints or []\n",
77+
" init_pars = model.config.suggested_init()\n",
78+
" fixed_params = model.config.suggested_fixed()\n",
79+
" \n",
80+
" for idx,fixed_val in constraints:\n",
81+
" init_pars[idx] = fixed_val\n",
82+
" fixed_params[idx] = True\n",
83+
" \n",
7584
" result = pyhf.infer.mle.fit(\n",
76-
" data, model, fixed_vals=constraints, return_uncertainties=True\n",
85+
" data, model, init_pars=init_pars, fixed_params=fixed_params, return_uncertainties=True\n",
7786
" )\n",
7887
" bestfit = result[:, 0]\n",
7988
" errors = result[:, 1]\n",

src/pyhf/infer/__init__.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66

77

88
def hypotest(
9-
poi_test, data, pdf, init_pars=None, par_bounds=None, qtilde=False, **kwargs
9+
poi_test,
10+
data,
11+
pdf,
12+
init_pars=None,
13+
par_bounds=None,
14+
fixed_params=None,
15+
qtilde=False,
16+
**kwargs,
1017
):
1118
r"""
1219
Compute :math:`p`-values and test statistics for a single value of the parameter of interest.
@@ -32,8 +39,9 @@ def hypotest(
3239
poi_test (Number or Tensor): The value of the parameter of interest (POI)
3340
data (Number or Tensor): The data considered
3441
pdf (~pyhf.pdf.Model): The statistical model adhering to the schema ``model.json``
35-
init_pars (Array or Tensor): The initial parameter values to be used for minimization
36-
par_bounds (Array or Tensor): The parameter value bounds to be used for minimization
42+
init_pars (`tensor`): The initial parameter values to be used for minimization
43+
par_bounds (`tensor`): The parameter value bounds to be used for minimization
44+
fixed_params (`tensor`): Whether to fix the parameter to the init_pars value during minimization
3745
qtilde (Bool): When ``True`` perform the calculation using the alternative
3846
test statistic, :math:`\tilde{q}_{\mu}`, as defined under the Wald
3947
approximation in Equation (62) of :xref:`arXiv:1007.1727`.
@@ -118,15 +126,19 @@ def hypotest(
118126
"""
119127
init_pars = init_pars or pdf.config.suggested_init()
120128
par_bounds = par_bounds or pdf.config.suggested_bounds()
121-
tensorlib, _ = get_backend()
129+
fixed_params = fixed_params or pdf.config.suggested_fixed()
122130

123-
calc = AsymptoticCalculator(data, pdf, init_pars, par_bounds, qtilde=qtilde)
131+
calc = AsymptoticCalculator(
132+
data, pdf, init_pars, par_bounds, fixed_params, qtilde=qtilde
133+
)
124134
teststat = calc.teststatistic(poi_test)
125135
sig_plus_bkg_distribution, b_only_distribution = calc.distributions(poi_test)
126136

127137
CLsb = sig_plus_bkg_distribution.pvalue(teststat)
128138
CLb = b_only_distribution.pvalue(teststat)
129139
CLs = CLsb / CLb
140+
141+
tensorlib, _ = get_backend()
130142
# Ensure that all CL values are 0-d tensors
131143
CLsb, CLb, CLs = (
132144
tensorlib.astensor(CLsb),

src/pyhf/infer/calculators.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from .test_statistics import qmu, qmu_tilde
1313

1414

15-
def generate_asimov_data(asimov_mu, data, pdf, init_pars, par_bounds):
15+
def generate_asimov_data(asimov_mu, data, pdf, init_pars, par_bounds, fixed_params):
1616
"""
1717
Compute Asimov Dataset (expected yields at best-fit values) for a given POI value.
1818
@@ -22,12 +22,15 @@ def generate_asimov_data(asimov_mu, data, pdf, init_pars, par_bounds):
2222
pdf (~pyhf.pdf.Model): The statistical model adhering to the schema ``model.json``.
2323
init_pars (`tensor`): The initial parameter values to be used for fitting.
2424
par_bounds (`tensor`): The parameter value bounds to be used for fitting.
25+
fixed_params (`tensor`): Parameters to be held constant in the fit.
2526
2627
Returns:
2728
Tensor: The Asimov dataset.
2829
2930
"""
30-
bestfit_nuisance_asimov = fixed_poi_fit(asimov_mu, data, pdf, init_pars, par_bounds)
31+
bestfit_nuisance_asimov = fixed_poi_fit(
32+
asimov_mu, data, pdf, init_pars, par_bounds, fixed_params
33+
)
3134
return pdf.expected_data(bestfit_nuisance_asimov)
3235

3336

@@ -118,7 +121,15 @@ def expected_value(self, nsigma):
118121
class AsymptoticCalculator(object):
119122
"""The Asymptotic Calculator."""
120123

121-
def __init__(self, data, pdf, init_pars=None, par_bounds=None, qtilde=False):
124+
def __init__(
125+
self,
126+
data,
127+
pdf,
128+
init_pars=None,
129+
par_bounds=None,
130+
fixed_params=None,
131+
qtilde=False,
132+
):
122133
"""
123134
Asymptotic Calculator.
124135
@@ -127,6 +138,7 @@ def __init__(self, data, pdf, init_pars=None, par_bounds=None, qtilde=False):
127138
pdf (~pyhf.pdf.Model): The statistical model adhering to the schema ``model.json``.
128139
init_pars (`tensor`): The initial parameter values to be used for fitting.
129140
par_bounds (`tensor`): The parameter value bounds to be used for fitting.
141+
fixed_params (`tensor`): Whether to fix the parameter to the init_pars value during minimization
130142
qtilde (`bool`): Whether to use qtilde as the test statistic.
131143
132144
Returns:
@@ -137,6 +149,8 @@ def __init__(self, data, pdf, init_pars=None, par_bounds=None, qtilde=False):
137149
self.pdf = pdf
138150
self.init_pars = init_pars or pdf.config.suggested_init()
139151
self.par_bounds = par_bounds or pdf.config.suggested_bounds()
152+
self.fixed_params = fixed_params or pdf.config.suggested_fixed()
153+
140154
self.qtilde = qtilde
141155
self.sqrtqmuA_v = None
142156

@@ -173,16 +187,31 @@ def teststatistic(self, poi_test):
173187
teststat_func = qmu_tilde if self.qtilde else qmu
174188

175189
qmu_v = teststat_func(
176-
poi_test, self.data, self.pdf, self.init_pars, self.par_bounds
190+
poi_test,
191+
self.data,
192+
self.pdf,
193+
self.init_pars,
194+
self.par_bounds,
195+
self.fixed_params,
177196
)
178197
sqrtqmu_v = tensorlib.sqrt(qmu_v)
179198

180199
asimov_mu = 0.0
181200
asimov_data = generate_asimov_data(
182-
asimov_mu, self.data, self.pdf, self.init_pars, self.par_bounds
201+
asimov_mu,
202+
self.data,
203+
self.pdf,
204+
self.init_pars,
205+
self.par_bounds,
206+
self.fixed_params,
183207
)
184208
qmuA_v = teststat_func(
185-
poi_test, asimov_data, self.pdf, self.init_pars, self.par_bounds
209+
poi_test,
210+
asimov_data,
211+
self.pdf,
212+
self.init_pars,
213+
self.par_bounds,
214+
self.fixed_params,
186215
)
187216
self.sqrtqmuA_v = tensorlib.sqrt(qmuA_v)
188217

src/pyhf/infer/mle.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ def twice_nll(pars, data, pdf):
4747
return -2 * pdf.logpdf(pars, data)
4848

4949

50-
def fit(data, pdf, init_pars=None, par_bounds=None, **kwargs):
50+
def fit(data, pdf, init_pars=None, par_bounds=None, fixed_params=None, **kwargs):
5151
r"""
52-
Run a unconstrained maximum likelihood fit.
52+
Run a maximum likelihood fit.
5353
This is done by minimizing the objective function :func:`~pyhf.infer.mle.twice_nll`
5454
of the model parameters given the observed data.
5555
This is used to produce the maximal likelihood :math:`L\left(\hat{\mu}, \hat{\boldsymbol{\theta}}\right)`
@@ -87,6 +87,7 @@ def fit(data, pdf, init_pars=None, par_bounds=None, **kwargs):
8787
pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json
8888
init_pars (`list`): Values to initialize the model parameters at for the fit
8989
par_bounds (`list` of `list`\s or `tuple`\s): The extrema of values the model parameters are allowed to reach in the fit
90+
fixed_params (`list`): Parameters to be held constant in the fit.
9091
kwargs: Keyword arguments passed through to the optimizer API
9192
9293
Returns:
@@ -96,10 +97,23 @@ def fit(data, pdf, init_pars=None, par_bounds=None, **kwargs):
9697
_, opt = get_backend()
9798
init_pars = init_pars or pdf.config.suggested_init()
9899
par_bounds = par_bounds or pdf.config.suggested_bounds()
99-
return opt.minimize(twice_nll, data, pdf, init_pars, par_bounds, **kwargs)
100+
fixed_params = fixed_params or pdf.config.suggested_fixed()
100101

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+
]
101108

102-
def fixed_poi_fit(poi_val, data, pdf, init_pars=None, par_bounds=None, **kwargs):
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+
):
103117
r"""
104118
Run a maximum likelihood fit with the POI value fixed.
105119
This is done by minimizing the objective function of :func:`~pyhf.infer.mle.twice_nll`
@@ -142,6 +156,7 @@ def fixed_poi_fit(poi_val, data, pdf, init_pars=None, par_bounds=None, **kwargs)
142156
pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json
143157
init_pars (`list`): Values to initialize the model parameters at for the fit
144158
par_bounds (`list` of `list`\s or `tuple`\s): The extrema of values the model parameters are allowed to reach in the fit
159+
fixed_params (`list`): Parameters to be held constant in the fit.
145160
kwargs: Keyword arguments passed through to the optimizer API
146161
147162
Returns:
@@ -152,15 +167,11 @@ def fixed_poi_fit(poi_val, data, pdf, init_pars=None, par_bounds=None, **kwargs)
152167
raise UnspecifiedPOI(
153168
'No POI is defined. A POI is required to fit with a fixed POI.'
154169
)
155-
_, opt = get_backend()
156-
init_pars = init_pars or pdf.config.suggested_init()
157-
par_bounds = par_bounds or pdf.config.suggested_bounds()
158-
return opt.minimize(
159-
twice_nll,
160-
data,
161-
pdf,
162-
init_pars,
163-
par_bounds,
164-
[(pdf.config.poi_index, poi_val)],
165-
**kwargs,
166-
)
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)

0 commit comments

Comments
 (0)