Skip to content

Commit 0f094a4

Browse files
authored
fix: Sync SciPy minimizer initialized parameter values with fixed values (#1053)
* Fix bug in the SciPy optimizer where the init_pars is not synced up with the fixed values - Update the initial values to the fixed value for any fixed parameter * Add tests for constraining values bounding initialization values
1 parent 5bf3242 commit 0f094a4

File tree

6 files changed

+81
-48
lines changed

6 files changed

+81
-48
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Hello World
4343
>>> test_mu = 1.0
4444
>>> CLs_obs, CLs_exp = pyhf.infer.hypotest(test_mu, data, model, qtilde=True, return_expected=True)
4545
>>> print(f"Observed: {CLs_obs}, Expected: {CLs_exp}")
46-
Observed: 0.052515541856109765, Expected: 0.06445521290832805
46+
Observed: 0.05251497423736956, Expected: 0.06445320535890459
4747
4848
What does it support
4949
--------------------

src/pyhf/infer/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ def hypotest(
2424
... test_poi, data, model, qtilde=True, return_expected_set=True
2525
... )
2626
>>> CLs_obs
27-
array(0.05251554)
27+
array(0.05251497)
2828
>>> CLs_exp_band
29-
[array(0.00260641), array(0.01382066), array(0.06445521), array(0.23526104), array(0.57304182)]
29+
[array(0.00260626), array(0.01382005), array(0.06445321), array(0.23525644), array(0.57303621)]
3030
3131
Args:
3232
poi_test (Number or Tensor): The value of the parameter of interest (POI)

src/pyhf/optimize/opt_scipy.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ def _minimize(
6060
values = [v for _, v in fixed_vals]
6161
if fixed_vals:
6262
constraints = [{'type': 'eq', 'fun': lambda v: v[indices] - values}]
63+
# update the initial values to the fixed value for any fixed parameter
64+
for idx, fixed_val in fixed_vals:
65+
x0[idx] = fixed_val
6366
else:
6467
constraints = []
6568

tests/test_optim.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,3 +434,23 @@ def test_stitch_pars(backend):
434434
0,
435435
60,
436436
]
437+
438+
439+
def test_init_pars_sync_fixed_values_scipy(mocker):
440+
opt = pyhf.optimize.scipy_optimizer()
441+
442+
minimizer = mocker.MagicMock()
443+
opt._minimize(minimizer, None, [9, 9, 9], fixed_vals=[(0, 1)])
444+
assert minimizer.call_args[0] == (None, [1, 9, 9])
445+
446+
447+
def test_init_pars_sync_fixed_values_minuit(mocker):
448+
opt = pyhf.optimize.minuit_optimizer()
449+
450+
# patch all we need
451+
from pyhf.optimize import opt_minuit
452+
453+
minimizer = mocker.patch.object(opt_minuit, 'iminuit')
454+
opt._get_minimizer(None, [9, 9, 9], [(0, 10)] * 3, fixed_vals=[(0, 1)])
455+
assert minimizer.Minuit.from_array_func.call_args[1]['start'] == [1, 9, 9]
456+
assert minimizer.Minuit.from_array_func.call_args[1]['fix'] == [True, False, False]

tests/test_regression.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,12 @@ def test_sbottom_regionA_1400_950_60(
7979
np.array(CLs_exp),
8080
np.array(
8181
[
82-
0.002644707461012826,
83-
0.013976754489151644,
84-
0.06497313811425813,
85-
0.23644505123524753,
86-
0.5744843501873754,
87-
]
82+
0.0026445531093281147,
83+
0.013976126501170727,
84+
0.06497105816950004,
85+
0.23644030478043676,
86+
0.5744785776763938,
87+
],
8888
),
8989
rtol=1e-5,
9090
)
@@ -103,17 +103,17 @@ def test_sbottom_regionA_1500_850_60(
103103
CLs_obs, CLs_exp = calculate_CLs(
104104
sbottom_regionA_bkgonly_json, sbottom_regionA_1500_850_60_patch_json
105105
)
106-
assert CLs_obs == pytest.approx(0.04536774062150508, rel=1e-5)
106+
assert CLs_obs == pytest.approx(0.045367205665400624, rel=1e-5)
107107
assert np.all(
108108
np.isclose(
109109
np.array(CLs_exp),
110110
np.array(
111111
[
112-
0.0059847029077065295,
113-
0.026103516126601122,
114-
0.10093985752614597,
115-
0.3101988586187604,
116-
0.6553686728646031,
112+
0.00598431785676406,
113+
0.026102240062850574,
114+
0.10093641492218848,
115+
0.31019245951964736,
116+
0.6553623337518385,
117117
]
118118
),
119119
rtol=1e-5,

tests/test_validation.py

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,13 @@ def expected_result_1bin_shapesys(mu=1.0):
5050
if mu == 1:
5151
expected_result = {
5252
"exp": [
53-
0.06371799398864626,
54-
0.15096503398048894,
55-
0.3279606950533305,
56-
0.6046087303039118,
57-
0.8662627605298466,
53+
0.06372011644331387,
54+
0.1509686618126131,
55+
0.3279657430196915,
56+
0.604613569829645,
57+
0.8662652332047568,
5858
],
59-
"obs": 0.4541865416107029,
59+
"obs": 0.45418892944576333,
6060
}
6161
return expected_result
6262

@@ -121,8 +121,14 @@ def spec_1bin_lumi():
121121
def expected_result_1bin_lumi(mu=1.0):
122122
if mu == 1:
123123
expected_result = {
124-
"exp": [0.01060338, 0.04022273, 0.13614217, 0.37078321, 0.71104119],
125-
"obs": 0.01047275,
124+
"exp": [
125+
0.01060400765567206,
126+
0.04022451457730529,
127+
0.13614632580079802,
128+
0.37078985531427255,
129+
0.7110468540175344,
130+
],
131+
"obs": 0.010473144401519705,
126132
}
127133
return expected_result
128134

@@ -189,13 +195,13 @@ def expected_result_1bin_normsys(mu=1.0):
189195
if mu == 1:
190196
expected_result = {
191197
"exp": [
192-
7.47169462e-10,
193-
5.7411551509088054e-08,
194-
3.6898088062731205e-06,
195-
0.00016965731538267896,
196-
0.004392708998555453,
198+
7.472581399417304e-10,
199+
5.741738272450336e-08,
200+
3.690120950161796e-06,
201+
0.00016966882793076826,
202+
0.004392935288879465,
197203
],
198-
"obs": 0.0006735317023683173,
204+
"obs": 0.0006735336290569807,
199205
}
200206
return expected_result
201207

@@ -262,7 +268,7 @@ def expected_result_2bin_histosys(mu=1):
262268
if mu == 1:
263269
expected_result = {
264270
"exp": [
265-
7.134513306138892e-06,
271+
7.133904244038431e-06,
266272
0.00012547100627138575,
267273
0.001880010666437615,
268274
0.02078964907605385,
@@ -352,13 +358,13 @@ def expected_result_2bin_2channel(mu=1.0):
352358
if mu == 1:
353359
expected_result = {
354360
"exp": [
355-
0.00043491354821983556,
356-
0.0034223000502860606,
357-
0.02337423265831151,
358-
0.1218654225510158,
359-
0.40382074249477845,
361+
0.0004349234603527283,
362+
0.003422361539161119,
363+
0.02337454317608372,
364+
0.12186650297311125,
365+
0.40382274594391104,
360366
],
361-
"obs": 0.056332621064982304,
367+
"obs": 0.0563327694384318,
362368
}
363369
return expected_result
364370

@@ -447,16 +453,20 @@ def spec_2bin_2channel_couplednorm(source=source_2bin_2channel_couplednorm()):
447453

448454
@pytest.fixture(scope='module')
449455
def expected_result_2bin_2channel_couplednorm(mu=1.0):
456+
# NB: mac/linux differ for exp[0]
457+
# Mac: 0.055222676184648795
458+
# Linux: 0.05522273289103311
459+
# Fill with midpoint of both values
450460
if mu == 1:
451461
expected_result = {
452462
"exp": [
453-
0.055223914655538435,
454-
0.13613239925395315,
455-
0.3068720101493323,
456-
0.5839470093910164,
457-
0.8554725461337025,
463+
0.05522270453784095,
464+
0.1361301880753241,
465+
0.30686879632329855,
466+
0.5839437910061168,
467+
0.8554708284963864,
458468
],
459-
"obs": 0.5906228034705155,
469+
"obs": 0.5906216823766879,
460470
}
461471
return expected_result
462472

@@ -569,11 +579,11 @@ def expected_result_2bin_2channel_coupledhistosys(mu=1.0):
569579
if mu == 1:
570580
expected_result = {
571581
"exp": [
572-
1.7653746536962154e-05,
573-
0.00026265644807799805,
574-
0.00334003612780065,
575-
0.031522353024659715,
576-
0.17907742915143962,
582+
1.7654378902209275e-05,
583+
0.00026266409358853543,
584+
0.0033401113778672156,
585+
0.03152286332324451,
586+
0.17907927340107824,
577587
],
578588
"obs": 0.07967400132261188,
579589
}
@@ -703,7 +713,7 @@ def validate_hypotest(pdf, data, mu_test, expected_result, tolerance=1e-6):
703713
)
704714
assert abs(CLs_obs - expected_result['obs']) / expected_result['obs'] < tolerance
705715
for result, expected in zip(CLs_exp_set, expected_result['exp']):
706-
assert abs(result - expected) / expected < tolerance
716+
assert abs(result - expected) / expected < tolerance, result
707717

708718

709719
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)