Belle II Software  release-05-01-25
electrons_pdf.py
1 # !/usr/bin/env python3
2 
3 """ electrons_pdf.py : construct the pdf for electron 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_electron_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  eop_range = (0.0, 1.2)
23  gaus_mu_range = (0.8, 0.5, 1.5)
24  gaus_sigma_range = (0.01, 0.02, 0.1)
25  gaus_frac_range = (0.0, 1.0)
26  cb_mu_range = (1.0, 0.5, 1.5)
27  cb_sigma_range = (0.1, 0.0001, 0.5)
28  cb_alpha_range = (0.9, 0.2, 4.5)
29  cb_nn_range = (1.35, 0.5, 10)
30  if (kwargs["pmin"] < 5.5e3):
31  gaus_sigma_range = (0.01, 0.005, 0.05)
32  if (kwargs["pmin"] < 4e3 or kwargs["charge"] > 0):
33  gaus_sigma_range = (0.01, 0.01, 0.05)
34  if kwargs["pmin"] == 4.5e3:
35  gaus_frac_range = (0.5, 1.0) # Impose Gaussian fraction to be larger than CB fraction.
36  if (kwargs["pmin"] < 0.5e3):
37  gaus_frac_range = (0.0, 0.2) # Impose Gaussian fraction to be smaller than CB fraction.
38 
39  # Open file w/ input E/p distribution.
40  inpath = "{0}/pdg{1}11.root".format(kwargs["inputpath"], append)
41  infile = ROOT.TFile(inpath)
42 
43  # Create variable to fit and its distribution in data.
44  eopvar = ROOT.RooRealVar("eop", "E/p", eop_range[0], eop_range[1])
45  eophist_data = infile.Get("h_Eop_{0}_{1}".format(idx_p - 1, idx_th - 1))
46  eopdata = ROOT.RooDataHist("eopdata", "eopdata", ROOT.RooArgList(eopvar), ROOT.RooFit.Import(eophist_data))
47 
48  # Create plot for the fit.
49  pm = "+" if kwargs["charge"] > 0 else "-"
50  particle = "e^{{{0}}}".format(pm)
51  p_range = "{0:.2f} < p_{{lab}} #leq {1:.2f} [GeV/c]".format(kwargs["pmin"] / 1e3, kwargs["pmax"] / 1e3)
52  theta_range = "{0:.1f} < #theta_{{lab}} #leq {1:.1f} [deg]".format(kwargs["thetamin"], kwargs["thetamax"])
53  title = "{0}, {1}, {2}".format(particle, p_range, theta_range)
54  frame1 = eopvar.frame(ROOT.RooFit.Title(title))
55  eopdata.plotOn(frame1, ROOT.RooFit.DataError(ROOT.RooAbsData.SumW2))
56 
57  # Create PDF to fit.
58  gaus_mean = ROOT.RooRealVar("#mu_{g}",
59  "mean of gaussian",
60  gaus_mu_range[0],
61  gaus_mu_range[1],
62  gaus_mu_range[2])
63  gaus_sigma = ROOT.RooRealVar("#sigma_{g}",
64  "width of gaussian",
65  gaus_sigma_range[0],
66  gaus_sigma_range[1],
67  gaus_sigma_range[2])
68  gaus_frac = ROOT.RooRealVar("frac_{g}",
69  "gaussian fraction",
70  gaus_frac_range[0],
71  gaus_frac_range[1])
72  gaus = ROOT.RooGaussian("gaus", "gaussian", eopvar, gaus_mean, gaus_sigma)
73 
74  cb_mu = ROOT.RooRealVar("#mu_{CB}",
75  "mean of CB",
76  cb_mu_range[0],
77  cb_mu_range[1],
78  cb_mu_range[2])
79  cb_sigma = ROOT.RooRealVar("#sigma_{CB}",
80  "width of CB shape",
81  cb_sigma_range[0],
82  cb_sigma_range[1],
83  cb_sigma_range[2])
84  cb_alpha = ROOT.RooRealVar("#alpha_{CB}",
85  "tail length",
86  cb_alpha_range[0],
87  cb_alpha_range[1],
88  cb_alpha_range[2])
89  cb_nn = ROOT.RooRealVar("nn_{CB}",
90  "tail slope",
91  cb_nn_range[0],
92  cb_nn_range[1],
93  cb_nn_range[2])
94  cb = ROOT.RooCBShape("cb", "crystal ball", eopvar, cb_mu, cb_sigma, cb_alpha, cb_nn)
95 
96  pdf = ROOT.RooAddPdf("pdf", "gaussian + crystal ball", gaus, cb, gaus_frac)
97 
98  # Ok, fit!
99  fitres = pdf.fitTo(eopdata, ROOT.RooFit.Save(), ROOT.RooFit.PrintEvalErrors(-1))
100 
101  # Extract the normalised post-fit PDF as TF1.
102  ral1 = ROOT.RooArgList(eopvar)
103  ral2 = ROOT.RooArgList(pdf.getParameters(ROOT.RooArgSet(eopvar)))
104  ras = ROOT.RooArgSet(eopvar)
105  pdffunc = pdf.asTF(ral1, ral2, ras)
106 
107  # Add the PDFs and the fitted parameters to the plot.
108  pdf.plotOn(frame1,
109  ROOT.RooFit.LineColor(ROOT.kBlack),
110  ROOT.RooFit.LineWidth(2),
111  ROOT.RooFit.MoveToBack())
112  pdf.plotOn(frame1,
113  ROOT.RooFit.Components("cb"),
114  ROOT.RooFit.LineStyle(ROOT.kDashed),
115  ROOT.RooFit.LineColor(ROOT.kTeal - 5),
116  ROOT.RooFit.LineWidth(2),
117  ROOT.RooFit.MoveToBack())
118  pdf.plotOn(frame1,
119  ROOT.RooFit.Components("gaus"),
120  ROOT.RooFit.LineStyle(ROOT.kSolid),
121  ROOT.RooFit.LineColor(ROOT.kGreen + 2),
122  ROOT.RooFit.LineWidth(2),
123  ROOT.RooFit.MoveToBack())
124  pdf.paramOn(frame1, ROOT.RooFit.Layout(0.135, 0.45, 0.75))
125 
126  # Create plot for fit residuals.
127  frame2 = eopvar.frame(ROOT.RooFit.Title("Residuals"))
128  frame2.addPlotable(frame1.residHist(), "P")
129 
130  # Check the goodness of fit.
131  ndf = get_ndf(eopdata, fitres)
132  print("X^2/ndf = {0:.3f} (ndf = {1})".format(frame1.chiSquare(), ndf))
133 
134  # Draw the plots and save them.
135  if kwargs["outputplots"]:
136  plotargs = dict(kwargs, frame1=frame1.Clone(), frame2=frame2.Clone(), ndf=ndf, ndiv=520, append=append)
137  draw_plots(**plotargs)
138 
139  pdfname = "pdf_EoP_{0}_{1}".format(idx_p, idx_th)
140  return pdffunc.Clone(pdfname) # Why cloning? A: https://root-forum.cern.ch/t/returning-th1f-from-function/17213/2