Belle II Software  release-05-01-25
protons_pdf.py
1 # !/usr/bin/env python3
2 
3 """ protons_pdf.py : construct the pdf for proton ECL charged PID. """
4 
5 __author__ = "Marco Milesi"
6 __email__ = "marco.milesi@unimelb.edu.au"
7 __maintainer__ = "Marco Milesi"
8 
9 import os
10 import ROOT
11 from plotting_utils import draw_plots, get_ndf
12 
13 
14 def fit_proton_eop(**kwargs):
15 
16  append = "anti" if kwargs["charge"] < 0 else ""
17 
18  idx_p = kwargs["idx_p"]
19  idx_th = kwargs["idx_theta"]
20 
21  # Set the PDF parameter ranges ((start), min, max).
22  if kwargs["pmin"] < 5.5e3:
23  # Bifurgaus for the peak, gaus0 for the right kink, gaus1 for the long right tail.
24  eop_range = (0.0, 1.0) if kwargs["charge"] < 0 else (0.0, 0.6)
25  bifurgaus_mu_range = (0.03, 0.01, 0.1)
26  bifurgaus_sigmaL_range = (0.01, 0.001, 0.1)
27  bifurgaus_sigmaR_range = (0.01, 0.001, 0.1)
28  bifurgaus_frac_range = (0.7, 1.0)
29  gaus0_mu_range = (0.07, 0.05, 0.2)
30  gaus0_sigma_range = (0.015, 0.001, 0.05)
31  gaus1_mu_range = (0.4, 0.1, 0.8) if kwargs["charge"] < 0 else (0.275, 0.1, 0.5)
32  gaus1_sigma_range = (0.1, 0.01, 0.7)
33  gaus1_frac_range = (0.0, 1.0)
34  if kwargs["pmin"] < 4.5e3:
35  eop_range = (0.0, 1.0) if kwargs["charge"] < 0 else (0.0, 0.6)
36  bifurgaus_mu_range = (0.039, 0.01, 0.05)
37  bifurgaus_sigmaL_range = (0.01, 0.001, 0.05)
38  bifurgaus_sigmaR_range = (0.01, 0.01, 0.1)
39  bifurgaus_frac_range = (0.7, 1.0)
40  gaus0_mu_range = (0.08, 0.065, 0.1)
41  gaus0_sigma_range = (0.015, 0.001, 0.1)
42  gaus1_mu_range = (0.4, 0.1, 0.8) if kwargs["charge"] < 0 else (0.275, 0.1, 0.5)
43  gaus1_sigma_range = (0.1, 0.01, 0.7)
44  gaus1_frac_range = (0.0, 1.0)
45  if kwargs["pmin"] < 4e3:
46  eop_range = (0.0, 1.0) if kwargs["charge"] < 0 else (0.0, 0.6)
47  bifurgaus_mu_range = (0.05, 0.01, 0.1)
48  bifurgaus_sigmaL_range = (0.01, 0.001, 0.05)
49  bifurgaus_sigmaR_range = (0.02, 0.001, 0.1)
50  bifurgaus_frac_range = (0.3, 1.0)
51  gaus0_mu_range = (0.075, 0.05, 0.2)
52  gaus0_sigma_range = (0.05, 0.01, 0.1)
53  gaus1_mu_range = (0.4, 0.1, 0.8) if kwargs["charge"] < 0 else (0.275, 0.1, 0.5)
54  gaus1_sigma_range = (0.1, 0.01, 0.7)
55  gaus1_frac_range = (0.0, 1.0)
56  if kwargs["pmin"] < 3e3:
57  eop_range = (0.0, 1.4) if kwargs["charge"] < 0 else (0.0, 0.6)
58  bifurgaus_mu_range = (0.075, 0.01, 0.15)
59  bifurgaus_sigmaL_range = (0.01, 0.001, 0.05)
60  bifurgaus_sigmaR_range = (0.02, 0.001, 0.1)
61  bifurgaus_frac_range = (0.3, 1.0)
62  gaus0_mu_range = (0.1, 0.05, 0.2)
63  gaus0_sigma_range = (0.05, 0.01, 0.1)
64  gaus1_mu_range = (0.6, 0.1, 0.8) if kwargs["charge"] < 0 else (0.2, 0.1, 0.5)
65  gaus1_sigma_range = (0.1, 0.01, 0.7)
66  gaus1_frac_range = (0.0, 1.0)
67  if kwargs["pmin"] < 2e3:
68  eop_range = (0.0, 1.6) if kwargs["charge"] < 0 else (0.0, 0.6)
69  bifurgaus_mu_range = (0.12, 0.01, 0.2)
70  bifurgaus_sigmaL_range = (0.01, 0.001, 0.05)
71  bifurgaus_sigmaR_range = (0.02, 0.001, 0.1)
72  bifurgaus_frac_range = (0.6, 1.0)
73  gaus0_mu_range = (0.15, 0.05, 0.4)
74  gaus0_sigma_range = (0.1, 0.01, 0.2)
75  gaus1_mu_range = (0.6, 0.1, 1.0) if kwargs["charge"] < 0 else (0.2, 0.1, 0.5)
76  gaus1_sigma_range = (0.3, 0.01, 1.0)
77  gaus1_frac_range = (0.0, 1.0)
78  if kwargs["pmin"] < 1.5e3:
79  eop_range = (0.0, 1.6) if kwargs["charge"] < 0 else (0.0, 0.6)
80  bifurgaus_mu_range = (0.13, 0.1, 0.3)
81  bifurgaus_sigmaL_range = (0.01, 0.001, 0.1) if kwargs["charge"] < 0 else (0.03, 0.015, 0.06)
82  bifurgaus_sigmaR_range = (0.05, 0.001, 0.1) if kwargs["charge"] < 0 else (0.03, 0.015, 0.06)
83  bifurgaus_frac_range = (0.5, 1.0)
84  gaus0_mu_range = (0.25, 0.2, 0.5)
85  gaus0_sigma_range = (0.1, 0.01, 0.2)
86  gaus1_mu_range = (0.8, 0.1, 1.0) if kwargs["charge"] < 0 else (0.2, 0.1, 0.4)
87  gaus1_sigma_range = (0.3, 0.01, 1.0)
88  gaus1_frac_range = (0.0, 1.0)
89  if kwargs["pmin"] < 1e3:
90  # Below this momentum threshold, E/p shapes are very different depending on +/-
91  eop_range = (0.0, 2.5) if kwargs["charge"] < 0 else (0.0, 0.6)
92  bifurgaus_mu_range = (0.4, 0.1, 0.6) if kwargs["charge"] < 0 else (0.4, 0.2, 0.6)
93  bifurgaus_sigmaL_range = (0.1, 0.001, 0.2)
94  bifurgaus_sigmaR_range = (0.05, 0.001, 0.1)
95  bifurgaus_frac_range = (0.0, 0.5) if kwargs["charge"] < 0 else (0.5, 1.0)
96  gaus0_mu_range = (0.5, 0.35, 0.65) if kwargs["charge"] < 0 else (0.35, 0.1, 0.4)
97  gaus0_sigma_range = (0.1, 0.01, 0.2)
98  gaus1_mu_range = (1.3, 0.8, 2) if kwargs["charge"] < 0 else (0.25, 0.1, 0.4)
99  gaus1_sigma_range = (0.3, 0.01, 0.7) if kwargs["charge"] < 0 else (0.15, 0.01, 0.3)
100  gaus1_frac_range = (0.0, 1.0)
101  if kwargs["pmin"] < 0.75e3:
102  eop_range = (0.0, 2.8) if kwargs["charge"] < 0 else (0.0, 0.5)
103  bifurgaus_mu_range = (0.4, 0.1, 0.6) if kwargs["charge"] < 0 else (0.35, 0.2, 0.6)
104  bifurgaus_sigmaL_range = (0.1, 0.001, 0.2)
105  bifurgaus_sigmaR_range = (0.05, 0.001, 0.1) if kwargs["charge"] < 0 else (0.03, 0.02, 0.06)
106  bifurgaus_frac_range = (0.0, 0.5) if kwargs["charge"] < 0 else (0.5, 1.0)
107  gaus0_mu_range = (0.65, 0.35, 0.85) if kwargs["charge"] < 0 else (0.2, 0.1, 0.3)
108  gaus0_sigma_range = (0.1, 0.01, 0.2)
109  gaus1_mu_range = (1.4, 0.8, 2) if kwargs["charge"] < 0 else (0.2, 0.1, 0.4)
110  gaus1_sigma_range = (0.3, 0.01, 0.8) if kwargs["charge"] < 0 else (0.15, 0.01, 0.3)
111  gaus1_frac_range = (0.0, 1.0)
112  if kwargs["pmin"] < 0.5e3:
113  eop_range = (0.0, 2.6) if kwargs["charge"] < 0 else (0.0, 0.4)
114  bifurgaus_mu_range = (0.1, 0.01, 0.2) if kwargs["charge"] < 0 else (0.15, 0.01, 0.3)
115  bifurgaus_sigmaL_range = (0.1, 0.001, 0.2)
116  bifurgaus_sigmaR_range = (0.05, 0.001, 0.1)
117  bifurgaus_frac_range = (0.0, 0.2) if kwargs["charge"] < 0 else (0.5, 1.0)
118  gaus0_mu_range = (0.65, 0.35, 0.85) if kwargs["charge"] < 0 else (0.15, 0.05, 0.2)
119  gaus0_sigma_range = (0.1, 0.01, 0.2)
120  gaus1_mu_range = (1.6, 1, 2.2) if kwargs["charge"] < 0 else (0.25, 0.1, 0.4)
121  gaus1_sigma_range = (0.3, 0.01, 0.7) if kwargs["charge"] < 0 else (0.15, 0.01, 0.3)
122  gaus1_frac_range = (0.0, 1.0) if kwargs["charge"] < 0 else (0.0, 0.0001)
123  if kwargs["pmin"] < 0.4e3:
124  eop_range = (0.0, 2.0) if kwargs["charge"] < 0 else (0.0, 0.4)
125  bifurgaus_mu_range = (0.1, 0.01, 0.2) if kwargs["charge"] < 0 else (0.075, 0.07, 0.09)
126  bifurgaus_sigmaL_range = (0.05, 0.001, 0.2) if kwargs["charge"] < 0 else (0.05, 0.01, 0.1)
127  bifurgaus_sigmaR_range = (0.05, 0.001, 0.1) if kwargs["charge"] < 0 else (0.025, 0.02, 0.1)
128  bifurgaus_frac_range = (0.0, 0.5) if kwargs["charge"] < 0 else (0.0, 1.0)
129  gaus0_mu_range = (0.45, 0.35, 0.85) if kwargs["charge"] < 0 else (0.15, 0.12, 0.18)
130  gaus0_sigma_range = (0.1, 0.01, 0.2)
131  gaus1_mu_range = (1.3, 1, 2.2) if kwargs["charge"] < 0 else (0.25, 0.2, 0.4)
132  gaus1_sigma_range = (0.3, 0.01, 0.7) if kwargs["charge"] < 0 else (0.18, 0.1, 0.3)
133  gaus1_frac_range = (0.0, 1.0)
134 
135  # Open file w/ input E/p distribution.
136  inpath = "{0}/pdg{1}2212.root".format(kwargs["inputpath"], append)
137  infile = ROOT.TFile(inpath)
138  # Create variable to fit and its distribution in data.
139  eopvar = ROOT.RooRealVar("eop", "E/p", eop_range[0], eop_range[1])
140  eophist_data = infile.Get("h_Eop_{0}_{1}".format(idx_p - 1, idx_th - 1))
141  eopdata = ROOT.RooDataHist("eopdata", "eopdata", ROOT.RooArgList(eopvar), ROOT.RooFit.Import(eophist_data))
142 
143  # Create plot for the fit.
144  pm = "+" if kwargs["charge"] > 0 else "-"
145  particle = "p^{{{0}}}".format(pm)
146  p_range = "{0:.2f} < p_{{lab}} #leq {1:.2f} [GeV/c]".format(kwargs["pmin"] / 1e3, kwargs["pmax"] / 1e3)
147  theta_range = "{0:.1f} < #theta_{{lab}} #leq {1:.1f} [deg]".format(kwargs["thetamin"], kwargs["thetamax"])
148  title = "{0}, {1}, {2}".format(particle, p_range, theta_range)
149  frame1 = eopvar.frame(ROOT.RooFit.Title(title))
150  eopdata.plotOn(frame1, ROOT.RooFit.DataError(ROOT.RooAbsData.SumW2))
151 
152  # Create PDF to fit.
153  bifurgaus_mu = ROOT.RooRealVar("#mu_{bg}",
154  "mean of bifurcated gaussian",
155  bifurgaus_mu_range[0],
156  bifurgaus_mu_range[1],
157  bifurgaus_mu_range[2])
158  bifurgaus_sigmal = ROOT.RooRealVar("#sigma_{bg}_{L}",
159  "widthL of bifurcated gaussian",
160  bifurgaus_sigmaL_range[0],
161  bifurgaus_sigmaL_range[1],
162  bifurgaus_sigmaL_range[2])
163  bifurgaus_sigmar = ROOT.RooRealVar("#sigma_{bg}_{R}",
164  "widthR of bifurcated gaussian",
165  bifurgaus_sigmaR_range[0],
166  bifurgaus_sigmaR_range[1],
167  bifurgaus_sigmaR_range[2])
168  bifurgaus_frac = ROOT.RooRealVar("frac_{bg}",
169  "bifurcated gaussian fraction",
170  bifurgaus_frac_range[0],
171  bifurgaus_frac_range[1])
172  bifurgaus = ROOT.RooBifurGauss("bifurgaus", "bifurcated gaussian", eopvar, bifurgaus_mu, bifurgaus_sigmal, bifurgaus_sigmar)
173 
174  gaus0_mean = ROOT.RooRealVar("#mu_{g0}",
175  "mean of gaussian",
176  gaus0_mu_range[0],
177  gaus0_mu_range[1],
178  gaus0_mu_range[2])
179  gaus0_sigma = ROOT.RooRealVar("#sigma_{g0}",
180  "width of gaussian",
181  gaus0_sigma_range[0],
182  gaus0_sigma_range[1],
183  gaus0_sigma_range[2])
184  gaus0 = ROOT.RooGaussian("gaus0", "gaussian", eopvar, gaus0_mean, gaus0_sigma)
185 
186  pdf0 = ROOT.RooAddPdf("pdf0", "bifurcated gaussian + gaussian", bifurgaus, gaus0, bifurgaus_frac)
187 
188  gaus1_mean = ROOT.RooRealVar("#mu_{g1}",
189  "mean of gaussian",
190  gaus1_mu_range[0],
191  gaus1_mu_range[1],
192  gaus1_mu_range[2])
193  gaus1_sigma = ROOT.RooRealVar("#sigma_{g1}",
194  "width of gaussian",
195  gaus1_sigma_range[0],
196  gaus1_sigma_range[1],
197  gaus1_sigma_range[2])
198  gaus1_frac = ROOT.RooRealVar("frac_{g1}",
199  "gaussian fraction",
200  gaus1_frac_range[0],
201  gaus1_frac_range[1])
202  gaus1 = ROOT.RooGaussian("gaus1", "gaussian", eopvar, gaus1_mean, gaus1_sigma)
203 
204  pdf = ROOT.RooAddPdf("pdf", "bifurcated gaussian + gaussian + gaussian", pdf0, gaus1, gaus1_frac)
205 
206  # Ok, fit!
207  fitres = pdf.fitTo(eopdata, ROOT.RooFit.Save(), ROOT.RooFit.PrintEvalErrors(-1))
208 
209  # Extract the normalised post-fit PDF as TF1.
210  ral1 = ROOT.RooArgList(eopvar)
211  ral2 = ROOT.RooArgList(pdf.getParameters(ROOT.RooArgSet(eopvar)))
212  ras = ROOT.RooArgSet(eopvar)
213  pdffunc = pdf.asTF(ral1, ral2, ras)
214 
215  # Add the PDFs and the fitted parameters to the plot.
216  pdf.plotOn(frame1,
217  ROOT.RooFit.LineColor(ROOT.kBlack),
218  ROOT.RooFit.LineWidth(2),
219  ROOT.RooFit.MoveToBack())
220  pdf.plotOn(frame1,
221  ROOT.RooFit.Components("bifurgaus"),
222  ROOT.RooFit.LineStyle(ROOT.kDashed),
223  ROOT.RooFit.LineColor(ROOT.kMagenta),
224  ROOT.RooFit.LineWidth(2),
225  ROOT.RooFit.MoveToBack())
226  pdf.plotOn(frame1,
227  ROOT.RooFit.Components("gaus0"),
228  ROOT.RooFit.LineStyle(ROOT.kSolid),
229  ROOT.RooFit.LineColor(ROOT.kMagenta - 9),
230  ROOT.RooFit.LineWidth(2),
231  ROOT.RooFit.MoveToBack())
232  pdf.plotOn(frame1,
233  ROOT.RooFit.Components("gaus1"),
234  ROOT.RooFit.LineStyle(ROOT.kDotted),
235  ROOT.RooFit.LineColor(ROOT.kPink - 2),
236  ROOT.RooFit.LineWidth(2),
237  ROOT.RooFit.MoveToBack())
238  pdf.paramOn(frame1, ROOT.RooFit.Layout(0.55, 0.9, 0.75))
239 
240  # Create plot for fit residuals.
241  frame2 = eopvar.frame(ROOT.RooFit.Title("Residuals"))
242  frame2.addPlotable(frame1.residHist(), "P")
243 
244  # Check the goodness of fit.
245  ndf = get_ndf(eopdata, fitres)
246  print("X^2/ndf = {0:.3f} (ndf = {1})".format(frame1.chiSquare(), ndf))
247 
248  # Draw the plots and save them.
249  if kwargs["outputplots"]:
250  plotargs = dict(kwargs, frame1=frame1.Clone(), frame2=frame2.Clone(), ndf=ndf, ndiv=520, append=append)
251  draw_plots(**plotargs)
252 
253  pdfname = "pdf_EoP_{0}_{1}".format(idx_p, idx_th)
254  return pdffunc.Clone(pdfname) # Why cloning? A: https://root-forum.cern.ch/t/returning-th1f-from-function/17213/2