Belle II Software  release-05-01-25
muons_pdf.py
1 # !/usr/bin/env python3
2 
3 """ muons_pdf.py : construct the pdf for muon 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 # WIP: fit showerE w/ Landau
14 
15 
16 def fit_muon_e(**kwargs):
17  pass
18 
19 
20 def fit_muon_eop(**kwargs):
21 
22  append = "anti" if kwargs["charge"] > 0 else ""
23 
24  idx_p = kwargs["idx_p"]
25  idx_th = kwargs["idx_theta"]
26 
27  # Set the PDF parameter ranges ((start), min, max).
28  if kwargs["pmin"] < 5.5e3:
29  eop_range = (0.0, 0.1)
30  bifurgaus_mu_range = (0.05, 0.0, 0.1)
31  bifurgaus_sigmaL_range = (0.003, 0.001, 0.005)
32  bifurgaus_sigmaR_range = (0.1, 0.005, 0.12)
33  bifurgaus_frac_range = (0.99, 1.0)
34  gaus_mu_range = (0.075, 0.05, 0.1)
35  gaus_sigma_range = (0.0, 0.02)
36  if kwargs["pmin"] < 4.5e3:
37  eop_range = (0.0, 0.1)
38  bifurgaus_mu_range = (0.05, 0.035, 0.06)
39  bifurgaus_sigmaL_range = (0.01, 0.005, 0.1)
40  bifurgaus_sigmaR_range = (0.1, 0.005, 0.12)
41  bifurgaus_frac_range = (0.99, 1.0)
42  gaus_mu_range = (0.075, 0.05, 0.1)
43  gaus_sigma_range = (0.0, 0.02)
44  if kwargs["pmin"] < 4e3:
45  eop_range = (0.0, 0.2)
46  bifurgaus_mu_range = (0.06, 0.0, 0.15)
47  bifurgaus_sigmaL_range = (0.055, 0.001, 0.01)
48  bifurgaus_sigmaR_range = (0.028, 0.005, 0.05)
49  bifurgaus_frac_range = (0.0, 1.0)
50  gaus_mu_range = (0.1, 0.06, 0.2)
51  gaus_sigma_range = (0.0, 0.04)
52  if kwargs["pmin"] < 3e3:
53  eop_range = (0.0, 0.2)
54  bifurgaus_mu_range = (0.085, 0.0, 0.15)
55  bifurgaus_sigmaL_range = (0.0125, 0.005, 0.03)
56  bifurgaus_sigmaR_range = (0.0075, 0.005, 0.02)
57  bifurgaus_frac_range = (0.0, 1.0)
58  gaus_mu_range = (0.1, 0.06, 0.2)
59  gaus_sigma_range = (0.0, 0.04)
60  if kwargs["pmin"] < 2e3:
61  eop_range = (0.0, 0.3)
62  bifurgaus_mu_range = (0.16, 0.0, 0.3)
63  bifurgaus_sigmaL_range = (0.019, 0.01, 0.1)
64  bifurgaus_sigmaR_range = (0.033, 0.005, 0.1)
65  bifurgaus_frac_range = (0.0, 1.0)
66  gaus_mu_range = (0.16, 0.0, 0.3)
67  gaus_sigma_range = (0.0, 0.1)
68  if kwargs["pmin"] < 1e3:
69  eop_range = (0.0, 0.6)
70  bifurgaus_mu_range = (0.25, 0.0, 0.45)
71  bifurgaus_sigmaL_range = (0.1, 0.01, 0.15)
72  bifurgaus_sigmaR_range = (0.1, 0.005, 0.15)
73  bifurgaus_frac_range = (0.0, 1.0)
74  gaus_mu_range = (0.35, 0.2, 0.6)
75  gaus_sigma_range = (0.0, 0.05)
76  if kwargs["pmin"] < 0.5e3:
77  eop_range = (0.0, 1.0)
78  bifurgaus_mu_range = (0.27, 0.0, 0.5)
79  bifurgaus_sigmaL_range = (0.145, 0.01, 0.3)
80  bifurgaus_sigmaR_range = (0.1475, 0.005, 0.3)
81  bifurgaus_frac_range = (0.0, 1.0)
82  gaus_mu_range = (0.43, 0.1, 1.0)
83  gaus_sigma_range = (0.0, 0.2)
84 
85  # Open file w/ input E/p distribution.
86  inpath = "{0}/pdg{1}13.root".format(kwargs["inputpath"], append)
87  infile = ROOT.TFile(inpath)
88 
89  # Create variable to fit and its distribution in data.
90  eopvar = ROOT.RooRealVar("eop", "E/p", eop_range[0], eop_range[1])
91  eophist_data = infile.Get("h_Eop_{0}_{1}".format(idx_p - 1, idx_th - 1))
92  eopdata = ROOT.RooDataHist("eopdata", "eopdata", ROOT.RooArgList(eopvar), ROOT.RooFit.Import(eophist_data))
93 
94  # Create plot for the fit.
95  pm = "+" if kwargs["charge"] > 0 else "-"
96  particle = "#mu^{{{0}}}".format(pm)
97  p_range = "{0:.2f} < p_{{lab}} #leq {1:.2f} [GeV/c]".format(kwargs["pmin"] / 1e3, kwargs["pmax"] / 1e3)
98  theta_range = "{0:.1f} < #theta_{{lab}} #leq {1:.1f} [deg]".format(kwargs["thetamin"], kwargs["thetamax"])
99  title = "{0}, {1}, {2}".format(particle, p_range, theta_range)
100  frame1 = eopvar.frame(ROOT.RooFit.Title(title))
101  eopdata.plotOn(frame1, ROOT.RooFit.DataError(ROOT.RooAbsData.SumW2))
102 
103  # Create PDF to fit.
104  bifurgaus_mu = ROOT.RooRealVar("#mu_{bg}",
105  "mean of bifurcated gaussian",
106  bifurgaus_mu_range[0],
107  bifurgaus_mu_range[1],
108  bifurgaus_mu_range[2])
109  bifurgaus_sigmal = ROOT.RooRealVar("#sigma_{bg}_{L}",
110  "widthL of bifurcated gaussian",
111  bifurgaus_sigmaL_range[0],
112  bifurgaus_sigmaL_range[1],
113  bifurgaus_sigmaL_range[2])
114  bifurgaus_sigmar = ROOT.RooRealVar("#sigma_{bg}_{R}",
115  "widthR of bifurcated gaussian",
116  bifurgaus_sigmaR_range[0],
117  bifurgaus_sigmaR_range[1],
118  bifurgaus_sigmaR_range[2])
119  bifurgaus_frac = ROOT.RooRealVar("frac_{bg}",
120  "bifurcated gaussian fraction",
121  bifurgaus_frac_range[0],
122  bifurgaus_frac_range[1])
123  bifurgaus = ROOT.RooBifurGauss("bifurgaus", "bifurcated gaussian", eopvar, bifurgaus_mu, bifurgaus_sigmal, bifurgaus_sigmar)
124 
125  gaus_mean = ROOT.RooRealVar("#mu_{g}",
126  "mean of gaussian",
127  gaus_mu_range[0],
128  gaus_mu_range[1],
129  gaus_mu_range[2])
130  gaus_sigma = ROOT.RooRealVar("#sigma_{g}",
131  "width of gaussian",
132  gaus_sigma_range[0],
133  gaus_sigma_range[1])
134  gaus = ROOT.RooGaussian("gaus", "gaussian", eopvar, gaus_mean, gaus_sigma)
135 
136  pdf = ROOT.RooAddPdf("pdf", "bifurcated gaussian + gaussian", bifurgaus, gaus, bifurgaus_frac)
137 
138  # Ok, fit!
139  fitres = pdf.fitTo(eopdata, ROOT.RooFit.Save(), ROOT.RooFit.PrintEvalErrors(-1))
140 
141  # Extract the normalised post-fit PDF as TF1.
142  ral1 = ROOT.RooArgList(eopvar)
143  ral2 = ROOT.RooArgList(pdf.getParameters(ROOT.RooArgSet(eopvar)))
144  ras = ROOT.RooArgSet(eopvar)
145  pdffunc = pdf.asTF(ral1, ral2, ras)
146 
147  # Add the PDFs and the fitted parameters to the plot.
148  pdf.plotOn(frame1,
149  ROOT.RooFit.LineColor(ROOT.kBlack),
150  ROOT.RooFit.LineWidth(2),
151  ROOT.RooFit.MoveToBack())
152  pdf.plotOn(frame1,
153  ROOT.RooFit.Components("bifurgaus"),
154  ROOT.RooFit.LineStyle(ROOT.kDashed),
155  ROOT.RooFit.LineColor(ROOT.kRed),
156  ROOT.RooFit.LineWidth(2),
157  ROOT.RooFit.MoveToBack())
158  pdf.plotOn(frame1,
159  ROOT.RooFit.Components("gaus"),
160  ROOT.RooFit.LineStyle(ROOT.kSolid),
161  ROOT.RooFit.LineColor(ROOT.kRed + 2),
162  ROOT.RooFit.LineWidth(2),
163  ROOT.RooFit.MoveToBack())
164  pdf.paramOn(frame1, ROOT.RooFit.Layout(0.55, 0.9, 0.75))
165 
166  # Create plot for fit residuals.
167  frame2 = eopvar.frame(ROOT.RooFit.Title("Residuals"))
168  frame2.addPlotable(frame1.residHist(), "P")
169 
170  # Check the goodness of fit.
171  ndf = get_ndf(eopdata, fitres)
172  print("X^2/ndf = {0:.3f} (ndf = {1})".format(frame1.chiSquare(), ndf))
173 
174  # Draw the plots and save them.
175  if kwargs["outputplots"]:
176  plotargs = dict(kwargs, frame1=frame1.Clone(), frame2=frame2.Clone(), ndf=ndf, ndiv=510, append=append)
177  draw_plots(**plotargs)
178 
179  pdfname = "pdf_EoP_{0}_{1}".format(idx_p, idx_th)
180  return pdffunc.Clone(pdfname) # Why cloning? A: https://root-forum.cern.ch/t/returning-th1f-from-function/17213/2