12
12
## along with this program. if not, see <https://www.gnu.org/licenses/>. ##
13
13
#############################################################################
14
14
15
+ """Definition of the RooFitter class and helper functions"""
16
+
15
17
from math import sqrt
16
18
17
19
import ROOT
22
24
# pylint: disable=too-few-public-methods, too-many-statements
23
25
# (temporary until we add more functionality)
24
26
class RooFitter :
27
+ """Fitter using Roofit for combined fits of invariant-mass distributions"""
28
+
25
29
def __init__ (self ):
26
30
ROOT .gErrorIgnoreLevel = ROOT .kError
27
31
ROOT .RooMsgService .instance ().setSilentMode (True )
28
32
ROOT .RooMsgService .instance ().setGlobalKillBelow (ROOT .RooFit .WARNING )
29
33
ROOT .RooMsgService .instance ().setGlobalKillBelow (ROOT .RooFit .ERROR )
30
34
31
- def fit_mass_new (self , hist , pdfnames , fit_spec , level , roows = None , plot = False ):
35
+ def fit_mass_new (
36
+ self , hist , pdfnames : dict , fit_spec : dict , level : str , roows : ROOT .RooWorkspace = None , plot : bool = False
37
+ ):
38
+ """New fit method"""
32
39
if hist .GetEntries () == 0 :
33
40
raise UserWarning ("Cannot fit histogram with no entries" )
34
41
ws = roows or ROOT .RooWorkspace ("ws" )
35
42
var_m = fit_spec .get ("var" , "m" )
36
43
37
- n_signal = RooRealVar ("n_signal" , "Number of signal events" , 1e7 , 0 , 1.e10 )
44
+ n_signal = RooRealVar ("n_signal" , "Number of signal events" , 1e7 , 0 , 1e10 )
38
45
n_background = RooRealVar ("n_background" , "Number of background events" , 1e7 , 0 , 1e10 )
39
46
47
+ model = None
40
48
for comp , spec in fit_spec .get ("components" , {}).items ():
41
49
fn = ws .factory (spec ["fn" ])
42
50
if comp == "model" :
43
51
model = fn
52
+ if model is None :
53
+ raise ValueError ("model not set" )
54
+
44
55
m = ws .var (var_m )
45
56
46
57
if level == "data" and USE_EXTMODEL :
@@ -54,8 +65,6 @@ def fit_mass_new(self, hist, pdfnames, fit_spec, level, roows=None, plot=False):
54
65
"model" , "Total model" , RooArgList (signal_pdf , background_pdf ), RooArgList (n_signal , n_background )
55
66
)
56
67
57
- # if range_m := fit_spec.get('range'):
58
- # m.setRange(range_m[0], range_m[1])
59
68
dh = ROOT .RooDataHist ("dh" , "dh" , [m ], Import = hist )
60
69
if range_m := fit_spec .get ("range" ):
61
70
m .setRange ("fit" , * range_m )
@@ -64,7 +73,9 @@ def fit_mass_new(self, hist, pdfnames, fit_spec, level, roows=None, plot=False):
64
73
if level == 'data' and USE_EXTMODEL :
65
74
for v in ws .allVars ():
66
75
v .setConstant (True )
67
- res = extmodel .fitTo (dh , Range = (range_m [0 ], range_m [1 ]), Save = True , PrintLevel = - 1 , Strategy = 1 , MaxCalls = 5000 )
76
+ res = extmodel .fitTo (
77
+ dh , Range = (range_m [0 ], range_m [1 ]), Save = True , PrintLevel = - 1 , Strategy = 1 , MaxCalls = 5000
78
+ )
68
79
else :
69
80
res = model .fitTo (dh , Save = True , PrintLevel = - 1 , Strategy = 1 , MaxCalls = 5000 )
70
81
if level == 'data' and USE_EXTMODEL :
@@ -83,7 +94,8 @@ def fit_mass_new(self, hist, pdfnames, fit_spec, level, roows=None, plot=False):
83
94
model .paramOn (frame , Layout = (0.65 , 1.0 , 0.9 ))
84
95
frame .getAttText ().SetTextFont (42 )
85
96
frame .getAttText ().SetTextSize (0.001 )
86
- frame .SetAxisRange (range_m [0 ], range_m [1 ], "X" )
97
+ if range_m :
98
+ frame .SetAxisRange (range_m [0 ], range_m [1 ], "X" )
87
99
frame .SetAxisRange (0.0 , frame .GetMaximum () + (frame .GetMaximum () * 0.3 ), "Y" )
88
100
89
101
try :
@@ -99,8 +111,7 @@ def fit_mass_new(self, hist, pdfnames, fit_spec, level, roows=None, plot=False):
99
111
)
100
112
# model.SetName("bkg")
101
113
model .plotOn (frame , ROOT .RooFit .Name ("model" ))
102
- # pylint: disable=bare-except
103
- except :
114
+ except : # pylint: disable=bare-except # noqa: E722
104
115
pass
105
116
# for comp in fit_spec.get('components', {}):
106
117
# if comp != 'model':
@@ -109,7 +120,7 @@ def fit_mass_new(self, hist, pdfnames, fit_spec, level, roows=None, plot=False):
109
120
# c.Modified()
110
121
# c.Update()
111
122
112
- if level == "data" and USE_EXTMODEL :
123
+ if level == "data" and USE_EXTMODEL and frame is not None :
113
124
residuals = frame .residHist ("data" , "pdf_bkg" )
114
125
residual_frame = m .frame ()
115
126
residual_frame .addPlotable (residuals , "P" )
@@ -123,19 +134,26 @@ def fit_mass_new(self, hist, pdfnames, fit_spec, level, roows=None, plot=False):
123
134
ROOT .RooFit .Normalization (1.0 , ROOT .RooAbsReal .RelativeExpected ),
124
135
)
125
136
126
- residual_frame .SetAxisRange (range_m [0 ], range_m [1 ], "X" )
137
+ if range_m :
138
+ residual_frame .SetAxisRange (range_m [0 ], range_m [1 ], "X" )
127
139
residual_frame .SetYTitle ("Residuals" )
128
140
129
141
return (res , ws , frame , residual_frame )
130
142
131
143
def fit_mass (self , hist , fit_spec , plot = False ):
144
+ """Old fit method"""
132
145
if hist .GetEntries () == 0 :
133
146
raise UserWarning ("Cannot fit histogram with no entries" )
134
147
ws = ROOT .RooWorkspace ("ws" )
148
+
149
+ model = None
135
150
for comp , spec in fit_spec .get ("components" , {}).items ():
136
151
ws .factory (spec ["fn" ])
137
152
if comp == "sum" :
138
153
model = ws .pdf (comp )
154
+ if model is None :
155
+ raise ValueError ("model not set" )
156
+
139
157
m = ws .var ("m" )
140
158
# m.setRange('full', 0., 3.)
141
159
dh = ROOT .RooDataHist ("dh" , "dh" , [m ], Import = hist )
@@ -154,6 +172,7 @@ def fit_mass(self, hist, fit_spec, plot=False):
154
172
155
173
156
174
def calc_signif (roows , res , pdfnames , param_names , mean_sgn , sigma_sgn ):
175
+ """Calculate significance, signal, background, signal/background ratio."""
157
176
if not USE_EXTMODEL :
158
177
return (0. , 0. , 0. , 0. , 0. , 0 , 0 , 0. )
159
178
f_sig = roows .pdf (pdfnames ["pdf_sig" ])
@@ -217,6 +236,7 @@ def calc_signif(roows, res, pdfnames, param_names, mean_sgn, sigma_sgn):
217
236
218
237
219
238
def create_text_info (x_1 , y_1 , x_2 , y_2 ):
239
+ """Create an info box for fit plots and set its style."""
220
240
text_info = TPaveText (x_1 , y_1 , x_2 , y_2 , "NDC" )
221
241
text_info .SetBorderSize (0 )
222
242
text_info .SetFillColor (0 ) # Transparent fill
@@ -230,6 +250,7 @@ def create_text_info(x_1, y_1, x_2, y_2):
230
250
231
251
232
252
def add_text_info_fit (text_info , frame , roows , param_names ):
253
+ """Add fit info on the info box."""
233
254
chi2 = frame .chiSquare ()
234
255
mean_sgn = roows .var (param_names ["gauss_mean" ])
235
256
sigma_sgn = roows .var (param_names ["gauss_sigma" ])
@@ -253,6 +274,7 @@ def add_text_info_fit(text_info, frame, roows, param_names):
253
274
254
275
255
276
def add_text_info_perf (text_info , sig , sig_err , bkg , bkg_err , s_over_b , s_over_b_err , signif , signif_err ):
277
+ """Add signal, background, signal/background and significance on the info box."""
256
278
text_info .AddText (f"S(3#sigma) = { sig :.0f} #pm { sig_err :.0f} " )
257
279
text_info .AddText (f"B(3#sigma) = { bkg :.0f} #pm { bkg_err :.0f} " )
258
280
text_info .AddText (f"S/B(3#sigma) = { s_over_b :.3f} #pm { s_over_b_err :.3f} " )
0 commit comments