Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
rf709_BarlowBeeston.py
Go to the documentation of this file.
1## \file
2## \ingroup tutorial_roofit_main
3## \notebook -js
4## Implementing the Barlow-Beeston method for taking into account the statistical
5## uncertainty of a Monte-Carlo fit template.
6##
7## \macro_image
8## \macro_code
9## \macro_output
10##
11## Based on a demo by Wouter Verkerke
12## \date June 2021
13## \author Jonas Rembser, Harshal Shende, Stephan Hageboeck (C++ version)
14
15import math
16
17import ROOT
18
19
20def create_constraints(gammas, samples, global_observables):
21 constraints = ROOT.RooArgSet()
22 for i, gamma in enumerate(gammas):
23 nom_name = "nom_" + gamma.GetName()
24 tau_name = gamma.GetName() + "_tau"
25 mean_name = gamma.GetName() + "_mean"
26 poisson_name = gamma.GetName() + "_constraint"
27
28 nom_val = sum(func.dataHist().weight(i) for func in samples)
29
30 # Like in HistFactory, the gamma of an empty bin is fixed and not
31 # constrained. Otherwise, it would be a flat direction of the likelihood.
32 # The other gammas get the relative Poisson uncertainty as initial error.
33 if nom_val == 0.0:
34 gamma.setConstant(True)
35 continue
36 gamma.setError(1.0 / math.sqrt(nom_val))
37
38 nominal = ROOT.RooRealVar(nom_name, nom_name, nom_val, 0.0, ROOT.RooNumber.infinity())
39 nominal.setConstant(True)
40 global_observables.add(nominal)
41
42 tau = ROOT.RooConstVar(tau_name, tau_name, nominal.getVal())
43
44 mean = ROOT.RooProduct(mean_name, mean_name, gamma, tau)
45
46 poisson = ROOT.RooPoisson(poisson_name, poisson_name, nominal, mean)
47
48 # The constraint pdf takes ownership of the objects it depends on, and
49 # the returned set takes ownership of the constraint pdf.
50 for helper in (nominal, tau, mean):
51 ROOT.SetOwnership(helper, False)
52 poisson.addOwnedComponents(ROOT.RooArgSet(nominal, tau, mean))
53
54 constraints.addOwned(poisson)
55 return constraints
56
57
58ROOT.RooMsgService.instance().getStream(1).removeTopic(ROOT.RooFit.NumIntegration)
59
60# First, construct a likelihood model with a Gaussian signal on top of a uniform background
61x = ROOT.RooRealVar("x", "x", -20, 20)
62x.setBins(25)
63
64meanG = ROOT.RooRealVar("meanG", "meanG", 1, -10, 10)
65sigG = ROOT.RooRealVar("sigG", "sigG", 1.5, -10, 10)
66g = ROOT.RooGaussian("g", "Gauss", x, meanG, sigG)
67u = ROOT.RooUniform("u", "Uniform", x)
68
69# Generate the data to be fitted
70sigData = g.generate(x, 50)
71bkgData = u.generate(x, 1000)
72
73sumData = ROOT.RooDataSet("sumData", "Gauss + Uniform", x)
74sumData.append(sigData)
75sumData.append(bkgData)
76
77# Make histogram templates for signal and background.
78# Let's take a signal distribution with low statistics and a more accurate
79# background distribution.
80# Normally, these come from Monte Carlo simulations, but we will just generate them.
81dh_sig = g.generateBinned(x, 50)
82dh_bkg = u.generateBinned(x, 10000)
83
84# ***** Case 0 - 'Rigid templates' *****
85
86# Construct histogram shapes for signal and background
87p_h_sig = ROOT.RooHistFunc("p_h_sig", "p_h_sig", x, dh_sig)
88p_h_bkg = ROOT.RooHistFunc("p_h_bkg", "p_h_bkg", x, dh_bkg)
89
90# Construct scale factors for adding the two distributions
91Asig0 = ROOT.RooRealVar("Asig", "Asig", 1, 0.01, 5000)
92Abkg0 = ROOT.RooRealVar("Abkg", "Abkg", 1, 0.01, 5000)
93
94# Construct the sum model
95model0 = ROOT.RooRealSumPdf("model0", "model0", [p_h_sig, p_h_bkg], [Asig0, Abkg0], True)
96
97# ***** Case 1 - 'Barlow Beeston' *****
98
99# Construct parameterized histogram shapes for signal and background
100gammas_sig1 = ROOT.ParamHistFunc.createParamSet("p_ph_sig_gamma", x.numBins(), 0, 10)
101gammas_bkg1 = ROOT.ParamHistFunc.createParamSet("p_ph_bkg_gamma", x.numBins(), 0, 10)
102
103p_phf_sig1 = ROOT.ParamHistFunc("p_phf_sig1", "p_phf_sig1", [x], gammas_sig1)
104p_phf_bkg1 = ROOT.ParamHistFunc("p_phf_bkg1", "p_phf_bkg1", [x], gammas_bkg1)
105
106p_p_sig1 = ROOT.RooProduct("p_p_sig1", "p_p_sig1", p_h_sig, p_phf_sig1)
107p_p_bkg1 = ROOT.RooProduct("p_p_bkg1", "p_p_bkg1", p_h_bkg, p_phf_bkg1)
108
109Asig1 = ROOT.RooRealVar("Asig", "Asig", 1, 0.01, 5000)
110Abkg1 = ROOT.RooRealVar("Abkg", "Abkg", 1, 0.01, 5000)
111
112# Construct the sum of these
113model1_tmp = ROOT.RooRealSumPdf("sp_ph", "sp_ph", [p_p_sig1, p_p_bkg1], [Asig1, Abkg1], True)
114
115# Construct the subsidiary poisson measurements constraining the histogram parameters
116# These ensure that the bin contents of the histograms are only allowed to vary within
117# the statistical uncertainty of the Monte Carlo.
118globs1 = ROOT.RooArgSet()
119constraints_sig1 = create_constraints(gammas_sig1, [p_h_sig], globs1)
120constraints_bkg1 = create_constraints(gammas_bkg1, [p_h_bkg], globs1)
121
122# Construct the joint model with template PDFs and constraints
123model1 = ROOT.RooProdPdf("model1", "model1", [model1_tmp, *constraints_sig1, *constraints_bkg1])
124
125# ***** Case 2 - 'Barlow Beeston' light (one parameter per bin for all samples) *****
126
127# Construct the histogram shapes, using the same parameters for signal and background.
128# There is one parameter per bin, such as `p_ph_gamma_bin_0`. This allows bin 0 to
129# fluctuate up and down. The SAME ParamHistFunc scales both the signal and the background
130# histogram, so the bins fluctuate synchronously. This reduces the number of parameters.
131gammas2 = ROOT.ParamHistFunc.createParamSet("p_ph_gamma", x.numBins(), 0, 10)
132
133p_phf2 = ROOT.ParamHistFunc("p_phf2", "p_phf2", [x], gammas2)
134
135p_p_sig2 = ROOT.RooProduct("p_p_sig2", "p_p_sig2", p_h_sig, p_phf2)
136p_p_bkg2 = ROOT.RooProduct("p_p_bkg2", "p_p_bkg2", p_h_bkg, p_phf2)
137
138Asig2 = ROOT.RooRealVar("Asig", "Asig", 1, 0.01, 5000)
139Abkg2 = ROOT.RooRealVar("Abkg", "Abkg", 1, 0.01, 5000)
140
141# As before, construct the sum of signal2 and background2
142model2_tmp = ROOT.RooRealSumPdf("sp_ph", "sp_ph", [p_p_sig2, p_p_bkg2], [Asig2, Abkg2], True)
143
144# Construct the subsidiary poisson measurements constraining the statistical fluctuations
145globs2 = ROOT.RooArgSet()
146constraints2 = create_constraints(gammas2, [p_h_sig, p_h_bkg], globs2)
147
148# Construct the joint model
149model2 = ROOT.RooProdPdf("model2", "model2", [model2_tmp, *constraints2])
150
151# ************ Fit all models to data and plot *********************
152
153result0 = model0.fitTo(sumData, PrintLevel=0, Save=True)
154result1 = model1.fitTo(sumData, PrintLevel=0, Save=True, GlobalObservables=globs1)
155result2 = model2.fitTo(sumData, PrintLevel=0, Save=True, GlobalObservables=globs2)
156
157can = ROOT.TCanvas("can", "", 1500, 600)
158can.Divide(3, 1)
159
160pt = ROOT.TPaveText(-19.5, 1, -2, 25)
161pt.SetFillStyle(0)
162pt.SetBorderSize(0)
163
164can.cd(1)
165frame = x.frame(Title="No template uncertainties")
166# Plot data to enable automatic determination of model0 normalisation:
167sumData.plotOn(frame)
168model0.plotOn(frame, LineColor="b", VisualizeError=result0)
169# Plot data again to show it on top of model0 error bands:
170sumData.plotOn(frame)
171# Plot model components
172model0.plotOn(frame, LineColor="b")
173model0.plotOn(frame, Components=[p_h_sig], LineColor="kAzure")
174model0.plotOn(frame, Components=[p_h_bkg], LineColor="r")
175model0.paramOn(frame)
176
177sigData.plotOn(frame, MarkerColor="b")
178frame.Draw()
179
180for text in [
181 "No template uncertainties",
182 "are taken into account.",
183 "This leads to low errors",
184 "for the parameters A, since",
185 "the only source of errors",
186 "are the data statistics.",
187]:
188 pt.AddText(text)
189pt.DrawClone()
190
191can.cd(2)
192frame = x.frame(Title="Barlow Beeston for Sig & Bkg separately")
193sumData.plotOn(frame)
194model1.plotOn(frame, LineColor="b", VisualizeError=result1)
195# Plot data again to show it on top of error bands:
196sumData.plotOn(frame)
197model1.plotOn(frame, LineColor="b")
198model1.plotOn(frame, Components=[p_p_sig1], LineColor="kAzure")
199model1.plotOn(frame, Components=[p_p_bkg1], LineColor="r")
200model1.paramOn(frame, Parameters=[Asig1, Abkg1])
201
202sigData.plotOn(frame, MarkerColor="b")
203frame.Draw()
204
205pt.Clear()
206for text in [
207 "With gamma parameters, the",
208 "signal & background templates",
209 "can adapt to the data.",
210 "Note how the blue signal",
211 "template changes its shape.",
212 "This leads to higher errors",
213 "of the scale parameters A.",
214]:
215 pt.AddText(text)
216pt.DrawClone()
217
218can.cd(3)
219frame = x.frame(Title="Barlow Beeston light for (Sig+Bkg)")
220sumData.plotOn(frame)
221model2.plotOn(frame, LineColor="b", VisualizeError=result2)
222# Plot data again to show it on top of model0 error bands:
223sumData.plotOn(frame)
224model2.plotOn(frame, LineColor="b")
225model2.plotOn(frame, Components=[p_p_sig2], LineColor="kAzure")
226model2.plotOn(frame, Components=[p_p_bkg2], LineColor="r")
227model2.paramOn(frame, Parameters=[Asig2, Abkg2])
228
229sigData.plotOn(frame, MarkerColor="b")
230frame.Draw()
231
232pt.Clear()
233for text in [
234 "When signal and background",
235 "template share one gamma para-",
236 "meter per bin, they adapt less.",
237 "The errors of the A parameters",
238 "also shrink slightly.",
239]:
240 pt.AddText(text)
241pt.DrawClone()
242
243print(f"Asig [normal ] = {Asig0.getVal()} +/- {Asig0.getError()}")
244print(f"Asig [BB ] = {Asig1.getVal()} +/- {Asig1.getError()}")
245print(f"Asig [BBlight] = {Asig2.getVal()} +/- {Asig2.getError()}")
246
247can.SaveAs("rf709_BarlowBeeston.png")
static uint64_t sum(uint64_t i)
Definition Factory.cxx:2335