@@ -39,14 +39,39 @@ def norm_cdf(x, mu, sigma):
39
39
return (1 + np .vectorize (erf )(z )) * 0.5
40
40
41
41
42
- def mvnorm (mux , muy , sx , sy , rho ):
42
+ def mvnorm (mux , muy , sx , sy ):
43
43
stats = pytest .importorskip ("scipy.stats" )
44
- C = np .empty ((2 , 2 ))
45
- C [0 , 0 ] = sx ** 2
46
- C [0 , 1 ] = C [1 , 0 ] = sx * sy * rho
47
- C [1 , 1 ] = sy ** 2
48
- m = [mux , muy ]
49
- return stats .multivariate_normal (m , C )
44
+
45
+ # This used to be stats.multivariate_normal, but it's cdf
46
+ # is computed with a different algorithm since scipy 1.16.0
47
+ # and that broke our tests. There is no closed-form solution for
48
+ # the cdf of a bivariate normal distribution, so it's not a good
49
+ # test function anyway.
50
+ # Issue: https://github.com/scipy/scipy/issues/23469
51
+ # Potentially related: https://github.com/scipy/scipy/issues/23413
52
+ class mvnorm :
53
+ @staticmethod
54
+ def cdf (x_y ):
55
+ x , y = x_y .T
56
+ return stats .norm (mux , sx ).cdf (x ) * stats .norm (muy , sy ).cdf (y )
57
+
58
+ @staticmethod
59
+ def pdf (x_y ):
60
+ x , y = x_y .T
61
+ return stats .norm (mux , sx ).pdf (x ) * stats .norm (muy , sy ).pdf (y )
62
+
63
+ @staticmethod
64
+ def rvs (size = None , random_state = None ):
65
+ if random_state is None :
66
+ random_state = np .random .default_rng ()
67
+ return np .transpose (
68
+ [
69
+ stats .norm (mux , sx ).rvs (size = size , random_state = random_state ),
70
+ stats .norm (muy , sy ).rvs (size = size , random_state = random_state ),
71
+ ]
72
+ )
73
+
74
+ return mvnorm
50
75
51
76
52
77
def expon_cdf (x , a ):
@@ -289,16 +314,15 @@ def test_UnbinnedNLL_name_bad():
289
314
290
315
@pytest .mark .parametrize ("use_grad" , (False , True ))
291
316
def test_UnbinnedNLL_2D (use_grad ):
292
- def model (x_y , mux , muy , sx , sy , rho ):
293
- return mvnorm (mux , muy , sx , sy , rho ).pdf (x_y .T )
317
+ def model (x_y , mux , muy , sx , sy ):
318
+ return mvnorm (mux , muy , sx , sy ).pdf (x_y .T )
294
319
295
- truth = 0.1 , 0.2 , 0.3 , 0.4 , 0.5
320
+ truth = 0.1 , 0.2 , 0.3 , 0.4
296
321
x , y = mvnorm (* truth ).rvs (size = 100 , random_state = 1 ).T
297
322
298
323
cost = UnbinnedNLL ((x , y ), model , grad = numerical_model_gradient (model ))
299
324
m = Minuit (cost , * truth , grad = use_grad )
300
325
m .limits ["sx" , "sy" ] = (0 , None )
301
- m .limits ["rho" ] = (- 1 , 1 )
302
326
m .migrad ()
303
327
assert m .valid
304
328
@@ -384,10 +408,10 @@ def test_UnbinnedNLL_visualize(log):
384
408
def test_UnbinnedNLL_visualize_2D ():
385
409
pytest .importorskip ("matplotlib" )
386
410
387
- def model (x_y , mux , muy , sx , sy , rho ):
388
- return mvnorm (mux , muy , sx , sy , rho ).pdf (x_y .T )
411
+ def model (x_y , mux , muy , sx , sy ):
412
+ return mvnorm (mux , muy , sx , sy ).pdf (x_y .T )
389
413
390
- truth = 0.1 , 0.2 , 0.3 , 0.4 , 0.5
414
+ truth = 0.1 , 0.2 , 0.3 , 0.4
391
415
x , y = mvnorm (* truth ).rvs (size = 10 , random_state = 1 ).T
392
416
393
417
c = UnbinnedNLL ((x , y ), model )
@@ -466,10 +490,10 @@ def test_ExtendedUnbinnedNLL_name(unbinned):
466
490
467
491
@pytest .mark .parametrize ("use_grad" , (False , True ))
468
492
def test_ExtendedUnbinnedNLL_2D (use_grad ):
469
- def model (x_y , n , mux , muy , sx , sy , rho ):
470
- return n , n * mvnorm (mux , muy , sx , sy , rho ).pdf (x_y .T )
493
+ def model (x_y , n , mux , muy , sx , sy ):
494
+ return n , n * mvnorm (mux , muy , sx , sy ).pdf (x_y .T )
471
495
472
- truth = 100.0 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5
496
+ truth = 100.0 , 0.1 , 0.2 , 0.3 , 0.4
473
497
x , y = mvnorm (* truth [1 :]).rvs (size = int (truth [0 ]), random_state = 1 ).T
474
498
475
499
cost = ExtendedUnbinnedNLL (
@@ -478,7 +502,6 @@ def model(x_y, n, mux, muy, sx, sy, rho):
478
502
479
503
m = Minuit (cost , * truth , grad = use_grad )
480
504
m .limits ["n" , "sx" , "sy" ] = (0 , None )
481
- m .limits ["rho" ] = (- 1 , 1 )
482
505
m .migrad ()
483
506
assert m .valid
484
507
@@ -560,10 +583,10 @@ def model(x, s, mu, sigma):
560
583
def test_ExtendedUnbinnedNLL_visualize_2D ():
561
584
pytest .importorskip ("matplotlib" )
562
585
563
- def model (x_y , n , mux , muy , sx , sy , rho ):
564
- return n * 100 , n * 100 * mvnorm (mux , muy , sx , sy , rho ).pdf (x_y .T )
586
+ def model (x_y , n , mux , muy , sx , sy ):
587
+ return n * 100 , n * 100 * mvnorm (mux , muy , sx , sy ).pdf (x_y .T )
565
588
566
- truth = 1.0 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5
589
+ truth = 1.0 , 0.1 , 0.2 , 0.3 , 0.4
567
590
x , y = mvnorm (* truth [1 :]).rvs (size = int (truth [0 ] * 100 )).T
568
591
569
592
c = ExtendedUnbinnedNLL ((x , y ), model )
@@ -748,13 +771,13 @@ def test_BinnedNLL_ndof_zero():
748
771
749
772
@pytest .mark .parametrize ("use_grad" , (False , True ))
750
773
def test_BinnedNLL_2D (use_grad ):
751
- truth = (0.1 , 0.2 , 0.3 , 0.4 , 0.5 )
774
+ truth = (0.1 , 0.2 , 0.3 , 0.4 )
752
775
x , y = mvnorm (* truth ).rvs (size = 1000 , random_state = 1 ).T
753
776
754
777
w , xe , ye = np .histogram2d (x , y , bins = (20 , 50 ))
755
778
756
- def model (xy , mux , muy , sx , sy , rho ):
757
- return mvnorm (mux , muy , sx , sy , rho ).cdf (xy .T )
779
+ def model (xy , mux , muy , sx , sy ):
780
+ return mvnorm (mux , muy , sx , sy ).cdf (xy .T )
758
781
759
782
cost = BinnedNLL (w , (xe , ye ), model , grad = numerical_model_gradient (model ))
760
783
assert cost .ndata == np .prod (w .shape )
@@ -765,7 +788,6 @@ def model(xy, mux, muy, sx, sy, rho):
765
788
766
789
m = Minuit (cost , * truth , grad = use_grad )
767
790
m .limits ["sx" , "sy" ] = (0 , None )
768
- m .limits ["rho" ] = (- 1 , 1 )
769
791
m .migrad ()
770
792
assert m .valid
771
793
assert_allclose (m .values , truth , atol = 0.05 )
@@ -783,19 +805,18 @@ def model(xy, mux, muy, sx, sy, rho):
783
805
784
806
785
807
def test_BinnedNLL_2D_with_zero_bins ():
786
- truth = (0.1 , 0.2 , 0.3 , 0.4 , 0.5 )
808
+ truth = (0.1 , 0.2 , 0.3 , 0.4 )
787
809
x , y = mvnorm (* truth ).rvs (size = 1000 , random_state = 1 ).T
788
810
789
811
w , xe , ye = np .histogram2d (x , y , bins = (50 , 100 ), range = ((- 5 , 5 ), (- 5 , 5 )))
790
812
assert np .mean (w == 0 ) > 0.25
791
813
792
- def model (xy , mux , muy , sx , sy , rho ):
793
- return mvnorm (mux , muy , sx , sy , rho ).cdf (xy .T )
814
+ def model (xy , mux , muy , sx , sy ):
815
+ return mvnorm (mux , muy , sx , sy ).cdf (xy .T )
794
816
795
817
cost = BinnedNLL (w , (xe , ye ), model )
796
818
m = Minuit (cost , * truth )
797
819
m .limits ["sx" , "sy" ] = (0 , None )
798
- m .limits ["rho" ] = (- 1 , 1 )
799
820
m .migrad ()
800
821
assert m .valid
801
822
assert_allclose (m .values , truth , atol = 0.05 )
@@ -849,12 +870,12 @@ def test_BinnedNLL_visualize():
849
870
def test_BinnedNLL_visualize_2D ():
850
871
pytest .importorskip ("matplotlib" )
851
872
852
- truth = (0.1 , 0.2 , 0.3 , 0.4 , 0.5 )
873
+ truth = (0.1 , 0.2 , 0.3 , 0.4 )
853
874
x , y = mvnorm (* truth ).rvs (size = 10 , random_state = 1 ).T
854
875
w , xe , ye = np .histogram2d (x , y , bins = (50 , 100 ), range = ((- 5 , 5 ), (- 5 , 5 )))
855
876
856
- def model (xy , mux , muy , sx , sy , rho ):
857
- return mvnorm (mux , muy , sx , sy , rho ).cdf (xy .T )
877
+ def model (xy , mux , muy , sx , sy ):
878
+ return mvnorm (mux , muy , sx , sy ).cdf (xy .T )
858
879
859
880
c = BinnedNLL (w , (xe , ye ), model )
860
881
@@ -1025,13 +1046,13 @@ def test_ExtendedBinnedNLL_bad_input():
1025
1046
1026
1047
@pytest .mark .parametrize ("use_grad" , (False , True ))
1027
1048
def test_ExtendedBinnedNLL_2D (use_grad ):
1028
- truth = (1.0 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 )
1049
+ truth = (1.0 , 0.1 , 0.2 , 0.3 , 0.4 )
1029
1050
x , y = mvnorm (* truth [1 :]).rvs (size = int (truth [0 ] * 100 ), random_state = 1 ).T
1030
1051
1031
1052
w , xe , ye = np .histogram2d (x , y , bins = (10 , 20 ))
1032
1053
1033
- def model (xy , n , mux , muy , sx , sy , rho ):
1034
- return n * 100 * mvnorm (mux , muy , sx , sy , rho ).cdf (np .transpose (xy ))
1054
+ def model (xy , n , mux , muy , sx , sy ):
1055
+ return n * 100 * mvnorm (mux , muy , sx , sy ).cdf (np .transpose (xy ))
1035
1056
1036
1057
cost = ExtendedBinnedNLL (w , (xe , ye ), model , grad = numerical_model_gradient (model ))
1037
1058
assert cost .ndata == np .prod (w .shape )
@@ -1042,7 +1063,6 @@ def model(xy, n, mux, muy, sx, sy, rho):
1042
1063
1043
1064
m = Minuit (cost , * truth , grad = use_grad )
1044
1065
m .limits ["n" , "sx" , "sy" ] = (0 , None )
1045
- m .limits ["rho" ] = (- 1 , 1 )
1046
1066
m .migrad ()
1047
1067
assert m .valid
1048
1068
assert_allclose (m .values , truth , atol = 0.1 )
@@ -1056,27 +1076,26 @@ def model(xy, n, mux, muy, sx, sy, rho):
1056
1076
def test_ExtendedBinnedNLL_3D ():
1057
1077
norm = pytest .importorskip ("scipy.stats" ).norm
1058
1078
1059
- truth = (1.0 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 , 0. 6 , 0.7 )
1079
+ truth = (1.0 , 0.1 , 0.2 , 0.3 , 0.4 , 0.6 , 0.7 )
1060
1080
n = int (truth [0 ] * 10000 )
1061
1081
x , y = mvnorm (* truth [1 :- 2 ]).rvs (size = n ).T
1062
1082
z = norm (truth [- 2 ], truth [- 1 ]).rvs (size = n )
1063
1083
1064
1084
w , edges = np .histogramdd ((x , y , z ), bins = (5 , 10 , 20 ))
1065
1085
1066
- def model (xyz , n , mux , muy , sx , sy , rho , muz , sz ):
1086
+ def model (xyz , n , mux , muy , sx , sy , muz , sz ):
1067
1087
* xy , z = xyz
1068
1088
return (
1069
1089
n
1070
1090
* 10000
1071
- * mvnorm (mux , muy , sx , sy , rho ).cdf (np .transpose (xy ))
1091
+ * mvnorm (mux , muy , sx , sy ).cdf (np .transpose (xy ))
1072
1092
* norm (muz , sz ).cdf (z )
1073
1093
)
1074
1094
1075
1095
cost = ExtendedBinnedNLL (w , edges , model )
1076
1096
assert cost .ndata == np .prod (w .shape )
1077
1097
m = Minuit (cost , * truth )
1078
1098
m .limits ["n" , "sx" , "sy" , "sz" ] = (0 , None )
1079
- m .limits ["rho" ] = (- 1 , 1 )
1080
1099
m .migrad ()
1081
1100
assert m .valid
1082
1101
assert_allclose (m .values , truth , atol = 0.05 )
@@ -1118,13 +1137,13 @@ def model(x, s, slope):
1118
1137
def test_ExtendedBinnedNLL_visualize_2D ():
1119
1138
pytest .importorskip ("matplotlib" )
1120
1139
1121
- truth = (1.0 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 )
1140
+ truth = (1.0 , 0.1 , 0.2 , 0.3 , 0.4 )
1122
1141
x , y = mvnorm (* truth [1 :]).rvs (size = int (truth [0 ] * 1000 ), random_state = 1 ).T
1123
1142
1124
1143
w , xe , ye = np .histogram2d (x , y , bins = (10 , 20 ))
1125
1144
1126
- def model (xy , n , mux , muy , sx , sy , rho ):
1127
- return n * 1000 * mvnorm (mux , muy , sx , sy , rho ).cdf (np .transpose (xy ))
1145
+ def model (xy , n , mux , muy , sx , sy ):
1146
+ return n * 1000 * mvnorm (mux , muy , sx , sy ).cdf (np .transpose (xy ))
1128
1147
1129
1148
c = ExtendedBinnedNLL (w , (xe , ye ), model )
1130
1149
@@ -2062,26 +2081,26 @@ def test_Template_with_model():
2062
2081
2063
2082
2064
2083
def test_Template_with_model_2D ():
2065
- truth1 = (1.0 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 )
2084
+ truth1 = (1.0 , 0.1 , 0.2 , 0.1 , 0.4 )
2066
2085
x1 , y1 = mvnorm (* truth1 [1 :]).rvs (size = int (truth1 [0 ] * 1000 ), random_state = 1 ).T
2067
- truth2 = (1.0 , 0.2 , 0.1 , 0.4 , 0.3 , 0.0 )
2068
- x2 , y2 = mvnorm (* truth2 [1 :]).rvs (size = int (truth2 [0 ] * 1000 ), random_state = 1 ).T
2086
+ truth2 = (1.0 , 0.2 , 0.1 , 0.4 , 0.1 )
2087
+ x2 , y2 = mvnorm (* truth2 [1 :]).rvs (size = int (truth2 [0 ] * 1000 ), random_state = 2 ).T
2069
2088
2070
2089
x = np .append (x1 , x2 )
2071
2090
y = np .append (y1 , y2 )
2072
- w , xe , ye = np .histogram2d (x , y , bins = (3 , 5 ))
2091
+ w , xe , ye = np .histogram2d (x , y , bins = (10 , 10 ))
2073
2092
2074
- def model (xy , n , mux , muy , sx , sy , rho ):
2075
- return n * 1000 * mvnorm (mux , muy , sx , sy , rho ).cdf (np .transpose (xy ))
2093
+ def model (xy , n , mux , muy , sx , sy ):
2094
+ return n * 1000 * mvnorm (mux , muy , sx , sy ).cdf (np .transpose (xy ))
2076
2095
2077
- x3 , y3 = mvnorm (* truth2 [1 :]).rvs (size = int (truth2 [0 ] * 10000 ), random_state = 2 ).T
2096
+ x3 , y3 = mvnorm (* truth2 [1 :]).rvs (size = int (truth2 [0 ] * 10000 ), random_state = 3 ).T
2078
2097
template = np .histogram2d (x3 , y3 , bins = (xe , ye ))[0 ]
2079
2098
2080
2099
cost = Template (w , (xe , ye ), (model , template ))
2081
2100
assert cost .ndata == np .prod (w .shape )
2082
2101
m = Minuit (cost , * truth1 , 1 )
2083
- m .limits ["x0_n" , "x0_sx" , "x0_sy" ] = (0 , None )
2084
- m .limits ["x0_rho" ] = (- 1 , 1 )
2102
+ m .limits ["x0_n" , "x0_sx" , "x0_sy" , "x1" ] = (0 , None )
2103
+ m .limits ["x0_mux" , "x0_muy" ] = (xe [ 0 ], xe [ - 1 ]), ( ye [ 0 ], ye [ - 1 ] )
2085
2104
m .migrad ()
2086
2105
assert m .valid
2087
2106
assert_allclose (m .values , truth1 + (1e3 ,), rtol = 0.1 )
0 commit comments