Belle II Software development
ECLChargedPIDDataAnalysisValidationModule.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8#include <ecl/modules/eclChargedPIDDataAnalysisExpert/ECLChargedPIDDataAnalysisValidationModule.h>
9
10#include <ecl/dataobjects/ECLPidLikelihood.h>
11#include <mdst/dataobjects/ECLCluster.h>
12#include <mdst/dataobjects/MCParticle.h>
13#include <mdst/dataobjects/Track.h>
14
15#include <TEfficiency.h>
16
17using namespace Belle2;
18
19//-----------------------------------------------------------------
20// Register the Module
21//-----------------------------------------------------------------
22
23REG_MODULE(ECLChargedPIDDataAnalysisValidation);
24
25//-----------------------------------------------------------------
26// Implementation
27//-----------------------------------------------------------------
28
30{
31 // Set module properties
32 setDescription("This module dumps a set of histograms with ECL charged PID-related info used for validation, starting from an input file w/ particle-gun-generated charged stable particles (and antiparticles).");
33
34 // Default charged stable pdgIds (particles & antiparticles)
35 std::vector<int> defaultChargedPdgIds;
36 for (const auto& hypo : Const::chargedStableSet) {
37 defaultChargedPdgIds.push_back(hypo.getPDGCode());
38 defaultChargedPdgIds.push_back(-hypo.getPDGCode());
39 }
40
41 addParam("inputPdgIdList", m_inputPdgIdList,
42 "The list of (signed) pdgIds of the charged stable particles for which validation plots should be produced. Default is ALL charged stable particles.",
43 defaultChargedPdgIds);
44 addParam("mergeChargeOfPdgIds", m_mergeChargeOfPdgIds,
45 "The list of (unsigned) pdgIds of the charged stable particles for which particle and antiparticle should be merged together in the plots. Default is no merging, meaning separate plots are generated for +/- charged particles for each input pdgId.",
46 std::vector<unsigned int>());
47 addParam("outputFileName", m_outputFileName,
48 "The base name of the output file. The pdgId of the charged particle is appended to the name.",
49 std::string("ECLChargedPid"));
50 addParam("saveValidationTree", m_saveValidationTree,
51 "If this flag is set to True, save also the validation TTree. Default is False.",
52 bool(false));
53}
54
58
59
61{
62 B2INFO("Initialising ROOT objects...");
63
64 // Convert pdgId list to a set to remove any accidental repetitions.
65 m_inputPdgIdSet = std::set<int>(m_inputPdgIdList.begin(), m_inputPdgIdList.end());
66
67 // By default, do not merge particles and antiparticles together,
68 // unless a particle hypo is in the configured "merge" list.
69 for (const auto& hypo : Const::chargedStableSet) {
70 bool merge = (std::find(m_mergeChargeOfPdgIds.begin(), m_mergeChargeOfPdgIds.end(),
71 hypo.getPDGCode()) == m_mergeChargeOfPdgIds.end()) ? false : true;
72 if (merge) {
73 B2WARNING("For (unsigned) hypothesis " << hypo.getPDGCode() << ", validation plots will be merged for +/- charged particles.");
74 }
75 m_mergeChargeFlagByHypo.insert(std::pair<Const::ChargedStable, bool>(hypo, merge));
76 }
77
78 std::string chargedPdgIdStr;
79 std::string fname;
80
81 for (const auto& chargedPdgId : m_inputPdgIdSet) {
82
83 // Check if this pdgId is that of a legit Const::ChargedStable particle.
84 if (!isValidChargedPdg(std::abs(chargedPdgId))) {
85 B2FATAL("PDG: " << chargedPdgId << " in m_inputPdgIdSet is not that of a valid particle in Const::chargedStableSet! Aborting...");
86 }
87
88 const auto chargedHypo = Const::chargedStableSet.find(std::abs(chargedPdgId));
89
90 // If merging particles and antiparticles for this hypo, no need to loop twice:
91 // register one TTree for the '+' charged pdgId only.
92 if (m_mergeChargeFlagByHypo[chargedHypo] and chargedPdgId < 0) continue;
93
94 // Get the idx of this pdgId in the Const::chargedStableSet
95 auto chargedIdx = chargedHypo.getIndex();
96
97 if (chargedPdgId > 0) {
98 chargedPdgIdStr = std::to_string(chargedPdgId);
99 } else {
100 chargedPdgIdStr = "anti" + std::to_string(std::abs(chargedPdgId));
101 // Add offset to idx.
103 }
104
105 fname = m_outputFileName + "_" + chargedPdgIdStr + ".root";
106
107 m_outputFile[chargedIdx] = new TFile(fname.c_str(), "RECREATE");
108
109 m_tree[chargedIdx] = new TTree("ECLChargedPid", "ECLChargedPid");
110 m_tree[chargedIdx]->Branch("p", &m_p[chargedIdx], "p/F");
111 m_tree[chargedIdx]->Branch("pt", &m_pt[chargedIdx], "pt/F");
112 m_tree[chargedIdx]->Branch("trkTheta", &m_trkTheta[chargedIdx], "trkTheta/F");
113 m_tree[chargedIdx]->Branch("trkPhi", &m_trkPhi[chargedIdx], "trkPhi/F");
114 m_tree[chargedIdx]->Branch("clusterTheta", &m_clusterTheta[chargedIdx], "clusterTheta/F");
115 m_tree[chargedIdx]->Branch("clusterPhi", &m_clusterPhi[chargedIdx], "clusterPhi/F");
116 m_tree[chargedIdx]->Branch("clusterReg", &m_clusterReg[chargedIdx], "clusterReg/F");
117 m_tree[chargedIdx]->Branch("trackClusterMatch", &m_trackClusterMatch[chargedIdx], "trackClusterMatch/F");
118 m_tree[chargedIdx]->Branch("logl_sig", &m_logl_sig[chargedIdx], "logl_sig/F");
119 m_tree[chargedIdx]->Branch("logl_bkg", &m_logl_bkg[chargedIdx], "logl_bkg/F");
120 m_tree[chargedIdx]->Branch("deltalogl_sig_bkg", &m_deltalogl_sig_bkg[chargedIdx], "deltalogl_sig_bkg/F");
121 m_tree[chargedIdx]->Branch("pids_glob", &m_pids_glob[chargedIdx]);
122
123 }
124}
125
127{
128
129 for (const auto& chargedPdgId : m_inputPdgIdSet) {
130
131 const auto chargedHypo = Const::chargedStableSet.find(std::abs(chargedPdgId));
132
133 // If merging particles and antiparticles for this hypo, no need to loop twice:
134 // fill one TTree for the '+' charged pdgId only.
135 if (m_mergeChargeFlagByHypo[chargedHypo] and chargedPdgId < 0) continue;
136
137 // Get the idx of this pdgId in the Const::chargedStableSet
138 auto chargedIdx = chargedHypo.getIndex();
139
140 if (chargedPdgId < 0) {
141 // Add offset to idx for antiparticles.
143 }
144
145 // Initialise branches to unphysical values.
146 m_p[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
147 m_pt[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
148 m_trkTheta[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
149 m_trkPhi[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
150 m_clusterTheta[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
151 m_clusterPhi[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
152 m_clusterReg[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
153 m_trackClusterMatch[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
154 m_logl_sig[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
155 m_logl_bkg[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
156 m_deltalogl_sig_bkg[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
157 for (const auto& chargedStable : Const::chargedStableSet) {
158 m_pids_glob[chargedIdx][chargedStable.getIndex()] = std::numeric_limits<float>::quiet_NaN();
159 }
160
161 for (const auto& particle : m_MCParticles) {
162
163 if (!particle.hasStatus(MCParticle::c_PrimaryParticle)) continue; // Only check primaries.
164 if (particle.hasStatus(MCParticle::c_Initial)) continue; // Ignore initial particles.
165 if (particle.hasStatus(MCParticle::c_IsVirtual)) continue; // Ignore virtual particles.
166
167 // Skip all particles expect for the one of interest.
168 // If merging particles and antiparticles for this pdgId, use abs so both charges are considered for the MCParticles.
169 if (m_mergeChargeFlagByHypo[chargedHypo]) {
170 // Charge-agnostic check.
171 if (std::abs(particle.getPDG()) != std::abs(chargedPdgId)) continue;
172 } else {
173 // Charge-dependent check.
174 if (particle.getPDG() != chargedPdgId) continue;
175 }
176
177 // Get the matching track w/ max momentum.
178 int itrack(0);
179 int itrack_max(-1);
180 double p_max(-999.0);
181 for (const auto& track : particle.getRelationsFrom<Track>()) {
182 const auto fitRes = track.getTrackFitResultWithClosestMass(Const::pion);
183 if (!fitRes) continue;
184 if (fitRes->getMomentum().R() > p_max) {
185 p_max = fitRes->getMomentum().R();
186 itrack_max = itrack;
187 }
188 itrack++;
189 }
190 if (itrack_max < 0) continue; // Go to next particle if no track found.
191
192 const auto track = particle.getRelationsFrom<Track>()[itrack_max];
193 const auto fitRes = track->getTrackFitResultWithClosestMass(Const::pion);
194
195 m_p[chargedIdx] = p_max;
196 m_pt[chargedIdx] = fitRes->get4Momentum().Pt();
197 m_trkTheta[chargedIdx] = fitRes->get4Momentum().Theta();
198 m_trkPhi[chargedIdx] = fitRes->get4Momentum().Phi();
199
200 // Get the index of the ECL cluster matching this track.
201 int icluster_match(-1);
202 auto eclClusters = track->getRelationsTo<ECLCluster>();
203 for (unsigned int icluster(0); icluster < eclClusters.size(); ++icluster) {
204 const auto eclCluster = eclClusters[icluster];
205 if (!eclCluster->hasHypothesis(ECLCluster::EHypothesisBit::c_nPhotons)) continue;
206 if (!eclCluster->isTrack()) continue;
207 icluster_match = icluster;
208 break;
209 }
210 // If no cluster match, skip to next particle, but keep track of counter.
211 if (icluster_match < 0) {
212 m_trackClusterMatch[chargedIdx] = 0;
213 continue;
214 }
215
216 const auto eclCluster = eclClusters[icluster_match];
217
218 m_clusterTheta[chargedIdx] = eclCluster->getTheta();
219 m_clusterPhi[chargedIdx] = eclCluster->getPhi();
220 m_clusterReg[chargedIdx] = eclCluster->getDetectorRegion();
221
222 m_trackClusterMatch[chargedIdx] = 1;
223
224 // The "signal" likelihood corresponds to the current chargedPdgId.
225 const auto chargedStableSig = Const::chargedStableSet.find(std::abs(chargedPdgId));
226 // For deltaLogL, we do a binary comparison sig/bkg.
227 // If sig=pion, use bkg=kaon. Otherwise, bkg=pion.
228 const auto chargedStableBkg = (chargedStableSig == Const::pion) ? Const::kaon : Const::pion;
229
230 // Very unlikely, but random failures due to missing ECLPidLikelihood have been observed
231 // Let's continue if this is a nullptr
232 const auto eclLikelihood = track->getRelated<ECLPidLikelihood>();
233 if (not eclLikelihood)
234 continue;
235
236 double lh_sig = eclLikelihood->getLikelihood(chargedStableSig);
237 double lh_bkg = eclLikelihood->getLikelihood(chargedStableBkg);
238
239 m_logl_sig[chargedIdx] = log(lh_sig);
240 m_logl_bkg[chargedIdx] = log(lh_bkg);
241 m_deltalogl_sig_bkg[chargedIdx] = log(lh_bkg) - log(lh_sig);
242
243 // For the current charged particle candidate, store the global likelihood ratio for all hypotheses.
244 double lh_all(0);
245 for (const auto& chargedStable : Const::chargedStableSet) {
246 lh_all += eclLikelihood->getLikelihood(chargedStable);
247 }
248 for (const auto& chargedStable : Const::chargedStableSet) {
249 m_pids_glob[chargedIdx][chargedStable.getIndex()] = eclLikelihood->getLikelihood(chargedStable) / lh_all;
250 }
251
252 }
253
254 m_tree[chargedIdx]->Fill();
255
256 }
257}
258
260{
261
262 for (const auto& chargedPdgId : m_inputPdgIdSet) {
263
264 // Define the charged stable particle ("sample") corresponding to the current pdgId.
265 const auto chargedStableSample = Const::chargedStableSet.find(std::abs(chargedPdgId));
266
267 const auto mergeCharge = m_mergeChargeFlagByHypo[chargedStableSample];
268
269 // If partcile/antiparticle merging is active for this hypo, the results are stored for the '+' particle only.
270 if (mergeCharge && chargedPdgId <= 0) continue;
271
272 // Extract the sign of the charge.
273 const auto chargeSign = static_cast<int>(chargedPdgId / std::abs(chargedPdgId));
274
275 // What we call "signal" is equal to the sample under consideration.
276 const auto chargedStableSig = chargedStableSample;
277
278 // What we call "background" depends on the current "signal" particle hypothesis.
279 const auto chargedStableBkg = (chargedStableSig == Const::pion) ? Const::kaon : Const::pion;
280
281 // Get the idx of this sample's pdgId to retrieve the correct TTree and output TFile.
282 // Remember to add offset for antiparticles.
283 auto chargedSampleIdx = (chargeSign > 0) ? chargedStableSig.getIndex() : chargedStableSig.getIndex() +
285
286 m_outputFile[chargedSampleIdx]->cd();
287
288 auto pdgIdDesc = (mergeCharge) ? std::to_string(chargedPdgId) + " and -" + std::to_string(chargedPdgId) : std::to_string(
289 chargedPdgId);
290
291 // Add summary description of validation file content.
292 TNamed("Description", TString::Format("ECL Charged PID control plots for charged stable particles/antiparticles ; Sample PDG = %s",
293 pdgIdDesc.c_str()).Data()).Write();
294
295 // Dump plots of PID variables.
296 dumpPIDVars(m_tree[chargedSampleIdx], chargedStableSig, chargeSign, chargedStableBkg, mergeCharge);
297 // Dump plots of PID "signal" efficiency for this "sample".
298 dumpPIDEfficiencyFakeRate(m_tree[chargedSampleIdx], chargedStableSample, chargeSign, chargedStableSig, mergeCharge);
299 // For pions, dump also the pi->lep fake rate.
300 if (chargedStableSample == Const::pion) {
301 dumpPIDEfficiencyFakeRate(m_tree[chargedSampleIdx], chargedStableSample, chargeSign, Const::electron, mergeCharge);
302 dumpPIDEfficiencyFakeRate(m_tree[chargedSampleIdx], chargedStableSample, chargeSign, Const::muon, mergeCharge);
303 }
304 // Dump plots of matching efficiency for this "sample".
305 dumpTrkClusMatchingEfficiency(m_tree[chargedSampleIdx], chargedStableSample, chargeSign, mergeCharge);
306
307 // Write the TTree to file if requested.
309 m_tree[chargedSampleIdx]->Write();
310 }
311
312 m_outputFile[chargedSampleIdx]->Close();
313
314 }
315}
316
318 const int sigCharge, const Const::ChargedStable& bkgHypo, bool mergeSigCharge)
319{
320
321 // Get the idx and pdgId of the input sample particle.
322 // This corresponds by construction to the "signal" hypothesis for the likelihood and DeltaLogL.
323 const auto sigHypoIdx = sigHypo.getIndex();
324 const auto sigHypoPdgId = sigHypo.getPDGCode();
325
326 // Get the pdgId of the "background" hypothesis to test for DeltaLogL.
327 const auto bkgHypoPdgId = bkgHypo.getPDGCode();
328
329 // Access the "signal" hypothesis's PID component in the sample's
330 // TTree vector branch of global PID values via the idx.
331 TString pidSigBranch = TString::Format("pids_glob[%i]", sigHypoIdx);
332
333 // Histogram of global PID distribution for the sample particle's signal hypo.
334 TString h_pid_name = TString::Format("h_pid_sig_%i", sigHypoPdgId);
335 TH1F* h_pid = new TH1F(h_pid_name.Data(), h_pid_name.Data(), 50, -0.5, 1.2);
336 h_pid->GetXaxis()->SetTitle(TString::Format("Likelihood ratio (%i/ALL) (ECL)", sigHypoPdgId).Data());
337
338 // Histogram of deltalogl.
339 TString h_deltalogl_name = TString::Format("h_deltalogl_bkg_%i_sig_%i", bkgHypoPdgId, sigHypoPdgId);
340 double deltalogl_min = -20.0;
341 double deltalogl_max = 20.0;
342 TH1F* h_deltalogl = new TH1F(h_deltalogl_name.Data(), h_deltalogl_name.Data(), 40, deltalogl_min, deltalogl_max);
343 h_deltalogl->GetXaxis()->SetTitle(TString::Format("#Deltaln(L) (%i/%i) (ECL)", bkgHypoPdgId, sigHypoPdgId).Data());
344
345 // Histogram of track-cluster match flag.
346 TString h_trkclusmatch_name = TString::Format("h_trkclusmatch_sig_%i", sigHypoPdgId);
347 TH1F* h_trkclusmatch = new TH1F(h_trkclusmatch_name.Data(), h_trkclusmatch_name.Data(), 4, -1.5, 2.5);
348 h_trkclusmatch->GetXaxis()->SetTitle(TString::Format("Track-ECLCluster match (%i)", sigHypoPdgId).Data());
349
350 // Dump histos from TTree.
351 sampleTree->Project(h_pid_name.Data(), pidSigBranch.Data());
352 sampleTree->Project(h_deltalogl_name.Data(), "deltalogl_sig_bkg");
353 sampleTree->Project(h_trkclusmatch_name.Data(), "trackClusterMatch");
354
355 // Make sure the plots show the u/oflow.
356 paintUnderOverflow(h_pid);
357 paintUnderOverflow(h_deltalogl);
358 paintUnderOverflow(h_trkclusmatch);
359
360 h_pid->SetOption("HIST");
361 h_deltalogl->SetOption("HIST");
362 h_trkclusmatch->SetOption("HIST");
363
364 // MetaOptions string.
365 std::string metaopts("pvalue-warn=0.1,pvalue-error=0.01");
366 std::string shifteropt("");
367 // Electron plots should be visible to the shifter by default.
368 if (sigHypo == Const::electron) {
369 shifteropt = "shifter,";
370 }
371
372 auto pdgIdDesc = (!mergeSigCharge) ? std::to_string(sigHypoPdgId * sigCharge) : std::to_string(
373 sigHypoPdgId) + " and -" + std::to_string(sigHypoPdgId);
374
375 // Add histogram info.
376 h_pid->GetListOfFunctions()->Add(new TNamed("Description",
377 TString::Format("Sample PDG = %s ; ECL global PID(%i) distribution. U/O flow is added to first (last) bin.",
378 pdgIdDesc.c_str(),
379 sigHypoPdgId).Data()));
380 h_pid->GetListOfFunctions()->Add(new TNamed("Check",
381 "The more peaked at 1, the better. Non-zero O-flow indicates either failure of MC matching for reco tracks (unlikely), or failure of track-ECL-cluster matching (more likely). Both cases result in PID=nan."));
382 h_pid->GetListOfFunctions()->Add(new TNamed("Contact", "Marcel Hohmann. mhohmann@student.unimelb.edu.au"));
383 h_pid->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
384
385 h_deltalogl->GetListOfFunctions()->Add(new TNamed("Description",
386 TString::Format("Sample PDG = %s ; ECL distribution of binary $\\Delta log(L)$ = log(L(%i)) - log(L(%i)). U/O flow is added to first (last) bin.",
387 pdgIdDesc.c_str(),
388 bkgHypoPdgId,
389 sigHypoPdgId).Data()));
390 h_deltalogl->GetListOfFunctions()->Add(new TNamed("Check",
391 "Basic metric for signal/bkg separation. The more negative, the better separation is achieved. Non-zero U-flow indicates a non-normal PDF value (of sig OR bkg) for some p,clusterTheta range, which might be due to a non-optimal definition of the x-axis range of the PDF templates. Non-zero O-flow indicates either failure of MC matching for reco tracks (unlikely), or failure of track-ECL-cluster matching (more likely)."));
392 h_deltalogl->GetListOfFunctions()->Add(new TNamed("Contact", "Marcel Hohmann. mhohmann@student.unimelb.edu.au"));
393 h_deltalogl->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
394
395 h_trkclusmatch->GetListOfFunctions()->Add(new TNamed("Description",
396 TString::Format("Sample PDG = %s ; Track-ECLCluster match flag distribution.",
397 pdgIdDesc.c_str()).Data()));
398 h_trkclusmatch->GetListOfFunctions()->Add(new TNamed("Check",
399 "The more peaked at 1, the better. Non-zero population in the bins w/ flag != 0|1 indicates failure of MC matching for reco tracks. In such cases, flag=nan."));
400 h_trkclusmatch->GetListOfFunctions()->Add(new TNamed("Contact", "Priyanka Cheema. priyanka.cheema@kit.edu"));
401 h_trkclusmatch->GetListOfFunctions()->Add(new TNamed("MetaOptions", metaopts.c_str()));
402
403 h_pid->Write();
404 h_deltalogl->Write();
405 h_trkclusmatch->Write();
406
407 delete h_pid;
408 delete h_deltalogl;
409 delete h_trkclusmatch;
410
411}
412
413
415 const int sampleCharge, const Const::ChargedStable& sigHypo, bool mergeSampleCharge)
416{
417
418 // The ratio type: EFFICIENCY || FAKE RATE
419 const std::string ratioType = (sampleHypo == sigHypo) ? "Efficiency" : "FakeRate";
420
421 // Get the *signed* pdgId of the input sample particle.
422 const int sampleHypoPdgId = sampleHypo.getPDGCode() * sampleCharge;
423
424 // Get the idx and pdgId of the "signal" hypothesis to test.
425 const auto sigHypoIdx = sigHypo.getIndex();
426 const auto sigHypoPdgId = sigHypo.getPDGCode();
427
428 // Access the "signal" hypothesis's PID component in the sample's
429 // TTree vector branch of global PID values via the idx.
430 TString pidSigCut = TString::Format("pids_glob[%i] > %f", sigHypoIdx, c_PID);
431
432 // Histograms of p, clusterReg, clusterPhi... for "pass" (N, numerator) and "all" (D, denominator) events.
433 TString h_p_N_name = TString::Format("h_p_N_%i", sigHypoPdgId);
434 TString h_p_D_name = TString::Format("h_p_D_%i", sigHypoPdgId);
435 TH1F* h_p_N = new TH1F(h_p_N_name.Data(), "h_p_N", 10, 0.0, 5.0);
436 TH1F* h_p_D = new TH1F(h_p_D_name.Data(), "h_p_D", 10, 0.0, 5.0);
437
438 TString h_th_N_name = TString::Format("h_th_N_%i", sigHypoPdgId);
439 TString h_th_D_name = TString::Format("h_th_D_%i", sigHypoPdgId);
440 TH1F* h_th_N = new TH1F(h_th_N_name.Data(), "h_th_N", m_th_binedges.size() - 1, m_th_binedges.data());
441 TH1F* h_th_D = new TH1F(h_th_D_name.Data(), "h_th_D", m_th_binedges.size() - 1, m_th_binedges.data());
442
443 TString h_eclreg_N_name = TString::Format("h_eclreg_N_%i", sigHypoPdgId);
444 TString h_eclreg_D_name = TString::Format("h_eclreg_D_%i", sigHypoPdgId);
445 TH1F* h_eclreg_N = new TH1F(h_eclreg_N_name.Data(), "h_eclreg_N", 5, -0.5, 4.5);
446 TH1F* h_eclreg_D = new TH1F(h_eclreg_D_name.Data(), "h_eclreg_D", 5, -0.5, 4.5);
447
448 TString h_phi_N_name = TString::Format("h_phi_N_%i", sigHypoPdgId);
449 TString h_phi_D_name = TString::Format("h_phi_D_%i", sigHypoPdgId);
450 TH1F* h_phi_N = new TH1F(h_phi_N_name.Data(), "h_phi_N", 5, -3.14159, 3.14159);
451 TH1F* h_phi_D = new TH1F(h_phi_D_name.Data(), "h_phi_D", 5, -3.14159, 3.14159);
452
453 // Fill the histograms from the sample's TTree.
454
455 sampleTree->Project(h_p_N_name.Data(), "p", pidSigCut.Data());
456 sampleTree->Project(h_p_D_name.Data(), "p");
457
458 sampleTree->Project(h_th_N_name.Data(), "clusterTheta", pidSigCut.Data());
459 sampleTree->Project(h_th_D_name.Data(), "clusterTheta");
460
461 sampleTree->Project(h_eclreg_N_name.Data(), "clusterReg", pidSigCut.Data());
462 sampleTree->Project(h_eclreg_D_name.Data(), "clusterReg");
463 paintUnderOverflow(h_eclreg_N);
464 paintUnderOverflow(h_eclreg_D);
465
466 sampleTree->Project(h_phi_N_name.Data(), "clusterPhi", pidSigCut.Data());
467 sampleTree->Project(h_phi_D_name.Data(), "clusterPhi");
468
469 // Compute the efficiency/fake rate.
470
471 TString pid_glob_ratio_p_name = TString::Format("pid_glob_%i_%s__VS_p", sigHypoPdgId, ratioType.c_str());
472 TString pid_glob_ratio_th_name = TString::Format("pid_glob_%i_%s__VS_th", sigHypoPdgId, ratioType.c_str());
473 TString pid_glob_ratio_eclreg_name = TString::Format("pid_glob_%i_%s__VS_eclreg", sigHypoPdgId, ratioType.c_str());
474 TString pid_glob_ratio_phi_name = TString::Format("pid_glob_%i_%s__VS_phi", sigHypoPdgId, ratioType.c_str());
475
476 // MetaOptions string.
477 std::string metaopts("pvalue-warn=0.01,pvalue-error=0.001,nostats");
478 std::string shifteropt("");
479 // Electron plots should be visible to the shifter by default.
480 if (sampleHypo == Const::electron || sigHypo == Const::electron) {
481 shifteropt = "shifter,";
482 }
483
484 auto pdgIdDesc = (!mergeSampleCharge) ? std::to_string(sampleHypoPdgId) : std::to_string(std::abs(
485 sampleHypoPdgId)) + " and -" + std::to_string(std::abs(sampleHypoPdgId));
486
487 if (TEfficiency::CheckConsistency(*h_p_N, *h_p_D)) {
488
489 TEfficiency* t_pid_glob_ratio_p = new TEfficiency(*h_p_N, *h_p_D);
490 t_pid_glob_ratio_p->SetName(pid_glob_ratio_p_name.Data());
491 t_pid_glob_ratio_p->SetTitle(TString::Format("%s;p [GeV/c];#varepsilon/f", pid_glob_ratio_p_name.Data()).Data());
492
493 t_pid_glob_ratio_p->SetConfidenceLevel(0.683);
494 t_pid_glob_ratio_p->SetStatisticOption(TEfficiency::kBUniform);
495 t_pid_glob_ratio_p->SetPosteriorMode();
496
497 t_pid_glob_ratio_p->GetListOfFunctions()->Add(new TNamed("Description",
498 TString::Format("Sample PDG = %s ; %s of ECL global PID(%i) > %.2f as a function of $p_{trk}$.",
499 pdgIdDesc.c_str(),
500 ratioType.c_str(),
501 sigHypoPdgId,
502 c_PID).Data()));
503 t_pid_glob_ratio_p->GetListOfFunctions()->Add(new TNamed("Check",
504 "Shape should be consistent. Obviously, check for decreasing efficiency / increasing fake rate."));
505 t_pid_glob_ratio_p->GetListOfFunctions()->Add(new TNamed("Contact", "Marcel Hohmann. mhohmann@student.unimelb.edu.au"));
506 t_pid_glob_ratio_p->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
507
508 t_pid_glob_ratio_p->Write();
509
510 delete t_pid_glob_ratio_p;
511
512 }
513 if (TEfficiency::CheckConsistency(*h_th_N, *h_th_D)) {
514
515 TEfficiency* t_pid_glob_ratio_th = new TEfficiency(*h_th_N, *h_th_D);
516 t_pid_glob_ratio_th->SetName(pid_glob_ratio_th_name.Data());
517 t_pid_glob_ratio_th->SetTitle(TString::Format("%s;#theta_{cluster} [rad];#varepsilon/f", pid_glob_ratio_th_name.Data()).Data());
518
519 t_pid_glob_ratio_th->SetConfidenceLevel(0.683);
520 t_pid_glob_ratio_th->SetStatisticOption(TEfficiency::kBUniform);
521 t_pid_glob_ratio_th->SetPosteriorMode();
522
523 t_pid_glob_ratio_th->GetListOfFunctions()->Add(new TNamed("Description",
524 TString::Format("Sample PDG = %s ; %s of ECL global PID(%i) > %.2f as a function of $\\theta_{cluster}$.",
525 pdgIdDesc.c_str(),
526 ratioType.c_str(),
527 sigHypoPdgId,
528 c_PID).Data()));
529 t_pid_glob_ratio_th->GetListOfFunctions()->Add(new TNamed("Check",
530 "Shape should be consistent. Obviously, check for decreasing efficiency / increasing fake rate."));
531 t_pid_glob_ratio_th->GetListOfFunctions()->Add(new TNamed("Contact", "Marcel Hohmann. mhohmann@student.unimelb.edu.au"));
532 t_pid_glob_ratio_th->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
533
534 t_pid_glob_ratio_th->Write();
535
536 delete t_pid_glob_ratio_th;
537 }
538 if (TEfficiency::CheckConsistency(*h_eclreg_N, *h_eclreg_D)) {
539
540 TEfficiency* t_pid_glob_ratio_eclreg = new TEfficiency(*h_eclreg_N, *h_eclreg_D);
541 t_pid_glob_ratio_eclreg->SetName(pid_glob_ratio_eclreg_name.Data());
542 t_pid_glob_ratio_eclreg->SetTitle(TString::Format("%s;ECL Region;#varepsilon/f", pid_glob_ratio_eclreg_name.Data()).Data());
543
544 t_pid_glob_ratio_eclreg->SetConfidenceLevel(0.683);
545 t_pid_glob_ratio_eclreg->SetStatisticOption(TEfficiency::kBUniform);
546 t_pid_glob_ratio_eclreg->SetPosteriorMode();
547
548 t_pid_glob_ratio_eclreg->GetListOfFunctions()->Add(new TNamed("Description",
549 TString::Format("Sample PDG = %s ; %s of ECL global PID(%i) > %.2f as a function of ECL cluster region ($\\theta_{cluster}$). Regions are labelled: 0 (outside ECL acceptance), 1 (ECL FWD), 2 (ECL Barrel), 3 (ECL BWD), 4 (ECL FWD/BWD gaps).",
550 pdgIdDesc.c_str(),
551 ratioType.c_str(),
552 sigHypoPdgId,
553 c_PID).Data()));
554 t_pid_glob_ratio_eclreg->GetListOfFunctions()->Add(new TNamed("Check",
555 "Shape should be consistent. Obviously, check for decreasing efficiency / increasing fake rate."));
556 t_pid_glob_ratio_eclreg->GetListOfFunctions()->Add(new TNamed("Contact", "Marcel Hohmann. mhohmann@student.unimelb.edu.au"));
557 t_pid_glob_ratio_eclreg->GetListOfFunctions()->Add(new TNamed("MetaOptions", metaopts.c_str()));
558
559 t_pid_glob_ratio_eclreg->Write();
560
561 delete t_pid_glob_ratio_eclreg;
562
563 }
564 if (TEfficiency::CheckConsistency(*h_phi_N, *h_phi_D)) {
565
566 TEfficiency* t_pid_glob_ratio_phi = new TEfficiency(*h_phi_N, *h_phi_D);
567 t_pid_glob_ratio_phi->SetName(pid_glob_ratio_phi_name.Data());
568 t_pid_glob_ratio_phi->SetTitle(TString::Format("%s;#phi_{cluster} [rad];#varepsilon/f", pid_glob_ratio_phi_name.Data()).Data());
569
570 t_pid_glob_ratio_phi->SetConfidenceLevel(0.683);
571 t_pid_glob_ratio_phi->SetStatisticOption(TEfficiency::kBUniform);
572 t_pid_glob_ratio_phi->SetPosteriorMode();
573
574 t_pid_glob_ratio_phi->GetListOfFunctions()->Add(new TNamed("Description",
575 TString::Format("Sample PDG = %s ; %s of ECL global PID(%i) > %.2f as a function of $\\phi_{cluster}$.",
576 pdgIdDesc.c_str(),
577 ratioType.c_str(),
578 sigHypoPdgId,
579 c_PID).Data()));
580 t_pid_glob_ratio_phi->GetListOfFunctions()->Add(new TNamed("Check",
581 "Shape should be consistent. Obviously, check for decreasing efficiency / increasing fake rate."));
582 t_pid_glob_ratio_phi->GetListOfFunctions()->Add(new TNamed("Contact", "Marcel Hohmann. mhohmann@student.unimelb.edu.au"));
583 t_pid_glob_ratio_phi->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
584
585 t_pid_glob_ratio_phi->Write();
586
587 delete t_pid_glob_ratio_phi;
588 }
589
590 delete h_p_N;
591 delete h_p_D;
592 delete h_th_N;
593 delete h_th_D;
594 delete h_eclreg_N;
595 delete h_eclreg_D;
596 delete h_phi_N;
597 delete h_phi_D;
598
599}
600
601
603 const int sampleCharge, bool mergeSampleCharge)
604{
605
606 // Get the (unsigned) pdgId of the input sample particle.
607 const auto sampleHypoPdgId = sampleHypo.getPDGCode();
608
609 // Histograms of pt, clusterTheta, clusterPhi... for "pass" (N, numerator) and "all" (D, denominator) events.
610 TString h_pt_N_name = TString::Format("h_pt_N_%i", sampleHypoPdgId);
611 TString h_pt_D_name = TString::Format("h_pt_D_%i", sampleHypoPdgId);
612 TH1F* h_pt_N = new TH1F(h_pt_N_name.Data(), "h_pt_N", 10, 0.0, 5.0);
613 TH1F* h_pt_D = new TH1F(h_pt_D_name.Data(), "h_pt_D", 10, 0.0, 5.0);
614
615 TString h_th_N_name = TString::Format("h_th_N_%i", sampleHypoPdgId);
616 TString h_th_D_name = TString::Format("h_th_D_%i", sampleHypoPdgId);
617 TH1F* h_th_N = new TH1F(h_th_N_name.Data(), "h_th_N", m_th_binedges.size() - 1, m_th_binedges.data());
618 TH1F* h_th_D = new TH1F(h_th_D_name.Data(), "h_th_D", m_th_binedges.size() - 1, m_th_binedges.data());
619
620 TString h_phi_N_name = TString::Format("h_phi_N_%i", sampleHypoPdgId);
621 TString h_phi_D_name = TString::Format("h_phi_D_%i", sampleHypoPdgId);
622 TH1F* h_phi_N = new TH1F(h_phi_N_name.Data(), "h_phi_N", 5, -3.14159, 3.14159);
623 TH1F* h_phi_D = new TH1F(h_phi_D_name.Data(), "h_phi_D", 5, -3.14159, 3.14159);
624
625 TString match_cut_N("trackClusterMatch == 1");
626 TString match_cut_D("trackClusterMatch >= 0");
627
628 // Fill the histograms from the sample's TTree.
629
630 tree->Project(h_pt_N_name.Data(), "pt", match_cut_N.Data());
631 tree->Project(h_pt_D_name.Data(), "pt", match_cut_D.Data());
632
633 tree->Project(h_th_N_name.Data(), "trkTheta", match_cut_N.Data());
634 tree->Project(h_th_D_name.Data(), "trkTheta", match_cut_D.Data());
635
636 tree->Project(h_phi_N_name.Data(), "trkPhi", match_cut_N.Data());
637 tree->Project(h_phi_D_name.Data(), "trkPhi", match_cut_D.Data());
638
639 // Compute the efficiency.
640
641 TString match_eff_pt_name = TString::Format("trkclusmatch_%i_Efficiency__VS_pt", sampleHypoPdgId);
642 TString match_eff_th_name = TString::Format("trkclusmatch_%i_Efficiency__VS_th", sampleHypoPdgId);
643 TString match_eff_phi_name = TString::Format("trkclusmatch_%i_Efficiency__VS_phi", sampleHypoPdgId);
644
645 // MetaOptions string.
646 std::string metaopts("pvalue-warn=0.01,pvalue-error=0.001,nostats");
647 std::string shifteropt("");
648 // Electron plots should be visible to the shifter by default.
649 if (sampleHypo == Const::electron) {
650 shifteropt = "shifter,";
651 }
652
653 auto pdgIdDesc = (!mergeSampleCharge) ? std::to_string(sampleHypoPdgId * sampleCharge) : std::to_string(
654 sampleHypoPdgId) + " and -" + std::to_string(sampleHypoPdgId);
655
656 if (TEfficiency::CheckConsistency(*h_pt_N, *h_pt_D)) {
657
658 TEfficiency* t_match_eff_pt = new TEfficiency(*h_pt_N, *h_pt_D);
659 t_match_eff_pt->SetName(match_eff_pt_name.Data());
660 t_match_eff_pt->SetTitle(TString::Format("%s;p_{T}^{trk} [GeV/c];#varepsilon", match_eff_pt_name.Data()).Data());
661 t_match_eff_pt->SetTitle(match_eff_pt_name.Data());
662
663 t_match_eff_pt->SetConfidenceLevel(0.683);
664 t_match_eff_pt->SetStatisticOption(TEfficiency::kBUniform);
665 t_match_eff_pt->SetPosteriorMode();
666
667 t_match_eff_pt->GetListOfFunctions()->Add(new TNamed("Description",
668 TString::Format("Sample PDG = %s ; Efficiency of track-ECL-cluster matching as a function of $p_{T}^{trk}$.",
669 pdgIdDesc.c_str()).Data()));
670 t_match_eff_pt->GetListOfFunctions()->Add(new TNamed("Check",
671 "Shape should be consistent. Obviously, check for decreasing efficiency."));
672 t_match_eff_pt->GetListOfFunctions()->Add(new TNamed("Contact", "Priyanka Cheema. priyanka.cheema@kit.edu"));
673 t_match_eff_pt->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
674
675 t_match_eff_pt->Write();
676
677 delete t_match_eff_pt;
678
679 }
680 if (TEfficiency::CheckConsistency(*h_th_N, *h_th_D)) {
681
682 TEfficiency* t_match_eff_th = new TEfficiency(*h_th_N, *h_th_D);
683 t_match_eff_th->SetName(match_eff_th_name.Data());
684 t_match_eff_th->SetTitle(TString::Format("%s;#theta_{trk} [rad];#varepsilon", match_eff_th_name.Data()).Data());
685 t_match_eff_th->SetTitle(match_eff_th_name.Data());
686
687 t_match_eff_th->SetConfidenceLevel(0.683);
688 t_match_eff_th->SetStatisticOption(TEfficiency::kBUniform);
689 t_match_eff_th->SetPosteriorMode();
690
691 t_match_eff_th->GetListOfFunctions()->Add(new TNamed("Description",
692 TString::Format("Sample PDG = %s ; Efficiency of track-ECL-cluster matching as a function of $\\theta_{trk}$.",
693 pdgIdDesc.c_str()).Data()));
694 t_match_eff_th->GetListOfFunctions()->Add(new TNamed("Check",
695 "Shape should be consistent. Obviously, check for decreasing efficiency."));
696 t_match_eff_th->GetListOfFunctions()->Add(new TNamed("Contact", "Priyanka Cheema. priyanka.cheema@kit.edu"));
697 t_match_eff_th->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
698
699 t_match_eff_th->Write();
700
701 delete t_match_eff_th;
702
703 }
704 if (TEfficiency::CheckConsistency(*h_phi_N, *h_phi_D)) {
705
706 TEfficiency* t_match_eff_phi = new TEfficiency(*h_phi_N, *h_phi_D);
707 t_match_eff_phi->SetName(match_eff_phi_name.Data());
708 t_match_eff_phi->SetTitle(TString::Format("%s;#phi_{trk} [rad];#varepsilon", match_eff_phi_name.Data()).Data());
709
710 t_match_eff_phi->SetConfidenceLevel(0.683);
711 t_match_eff_phi->SetStatisticOption(TEfficiency::kBUniform);
712 t_match_eff_phi->SetPosteriorMode();
713
714 t_match_eff_phi->GetListOfFunctions()->Add(new TNamed("Description",
715 TString::Format("Sample PDG = %s ; Efficiency of track-ECL-cluster matching as a function of $\\phi_{trk}$.",
716 pdgIdDesc.c_str()).Data()));
717 t_match_eff_phi->GetListOfFunctions()->Add(new TNamed("Check",
718 "Shape should be consistent. Obviously, check for decreasing efficiency."));
719 t_match_eff_phi->GetListOfFunctions()->Add(new TNamed("Contact", "Priyanka Cheema. priyanka.cheema@kit.edu"));
720 t_match_eff_phi->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
721
722 t_match_eff_phi->Write();
723
724 delete t_match_eff_phi;
725 }
726
727 delete h_pt_N;
728 delete h_pt_D;
729 delete h_th_N;
730 delete h_th_D;
731 delete h_phi_N;
732 delete h_phi_D;
733
734}
735
736
738{
739
740 auto nentries = h->GetEntries();
741 auto nbins_vis = h->GetNbinsX();
742
743 // Get the content and error of first/last visible bin.
744 float bin_vis_first = h->GetBinContent(1);
745 float bin_vis_last = h->GetBinContent(nbins_vis);
746 float bin_vis_first_err = h->GetBinError(1);
747 float bin_vis_last_err = h->GetBinError(nbins_vis);
748
749 // Get the content and error of u/oflow bins.
750 float bin_uflow = h->GetBinContent(0);
751 float bin_oflow = h->GetBinContent(nbins_vis + 1);
752 float bin_uflow_err = h->GetBinError(0);
753 float bin_oflow_err = h->GetBinError(nbins_vis + 1);
754
755 // Reset first/last visible bins to include u/oflow.
756 h->SetBinContent(1, bin_vis_first + bin_uflow);
757 h->SetBinError(1, sqrt(bin_vis_first_err * bin_vis_first_err + bin_uflow_err * bin_uflow_err));
758 h->SetBinContent(nbins_vis, bin_vis_last + bin_oflow);
759 h->SetBinError(nbins_vis, sqrt(bin_vis_last_err * bin_vis_last_err + bin_oflow_err * bin_oflow_err));
760
761 // Reset total entries to the original value.
762 h->SetEntries(nentries);
763
764}
Provides a type-safe way to pass members of the chargedStableSet set.
Definition Const.h:590
static const unsigned int c_SetSize
Number of elements (for use in array bounds etc.)
Definition Const.h:616
int getPDGCode() const
PDG code.
Definition Const.h:474
int getIndex() const
This particle's index in the associated set.
Definition Const.h:462
static const ChargedStable muon
muon particle
Definition Const.h:661
static const ParticleSet chargedStableSet
set of charged stable particles
Definition Const.h:619
static const ChargedStable pion
charged pion particle
Definition Const.h:662
static const ChargedStable kaon
charged kaon particle
Definition Const.h:663
static const ChargedStable electron
electron particle
Definition Const.h:660
static bool isValidChargedPdg(const int pdg)
Check if the input pdgId is that of a valid charged stable particle.
std::vector< float > m_trackClusterMatch
Flag for track-cluster matching condition.
std::vector< float > m_th_binedges
Binning w/ variable bin size for track polar angle (in [rad]).
std::set< int > m_inputPdgIdSet
The pdgId set of the charged stable particles of interest.
std::vector< float > m_deltalogl_sig_bkg
Delta Log-likelihood "signal" vs.
static void paintUnderOverflow(TH1F *h)
Draw u/oflow content on top of first/last visible bin.
static constexpr float c_PID
Definition of the PID cut threshold to compute the efficiency.
std::vector< float > m_pt
Track transverse momentum in [GeV/c].
void dumpPIDEfficiencyFakeRate(TTree *sampleTree, const Const::ChargedStable &sampleHypo, const int sampleCharge, const Const::ChargedStable &sigHypo, bool mergeSampleCharge=false)
Dump PID efficiency / fake rate vs clusterTheta, clusterPhi, p... for a fixed cut on PID as previousl...
bool m_saveValidationTree
Save the TTree in the output file alongside the histograms.
virtual ~ECLChargedPIDDataAnalysisValidationModule() override
Destructor of the module.
std::vector< float > m_logl_bkg
Log-likelihood for the "background" particle hypothesis.
std::vector< float > m_clusterPhi
Cluster azimuthal angle in [rad].
std::vector< TFile * > m_outputFile
Output ROOT::TFile that contains the info to plot.
std::vector< float > m_clusterTheta
Cluster polar angle in [rad].
std::map< Const::ChargedStable, bool > m_mergeChargeFlagByHypo
A map to tell for each charged stable particle hypothesis whether particle and antiparticle should be...
std::vector< TTree * > m_tree
A ROOT::TTree filled with the info to make control plots.
std::vector< unsigned int > m_mergeChargeOfPdgIds
The (unsigned) pdgId list of the charged stable particles for which particle and antiparticle should ...
static void dumpPIDVars(TTree *sampleTree, const Const::ChargedStable &sigHypo, const int sigCharge, const Const::ChargedStable &bkgHypo, bool mergeSigCharge=false)
Dump PID vars.
std::vector< std::vector< float > > m_pids_glob
List of global PIDs, defined by the likelihood ratio:
void dumpTrkClusMatchingEfficiency(TTree *sampleTree, const Const::ChargedStable &sampleHypo, const int sampleCharge, bool mergeSampleCharge=false)
Dump track-to-ECL-cluster matching efficiency vs clusterTheta, clusterPhi, pt....
std::vector< float > m_trkPhi
Track azimuthal angle in [rad].
std::vector< float > m_logl_sig
Log-likelihood for the "signal" particle hypothesis.
std::vector< int > m_inputPdgIdList
The pdgId list of the charged stable particles of interest.
ECL cluster data.
Definition ECLCluster.h:27
@ c_nPhotons
CR is split into n photons (N1)
Definition ECLCluster.h:41
Container for likelihoods with ECL PID (ECLChargedPIDModule)
double getLikelihood(const Const::ChargedStable &type) const
returns exp(getLogLikelihood(type)) with sufficient precision.
@ c_Initial
bit 5: Particle is initial such as e+ or e- and not going to Geant4
Definition MCParticle.h:57
@ c_PrimaryParticle
bit 0: Particle is primary particle.
Definition MCParticle.h:47
@ c_IsVirtual
bit 4: Particle is virtual and not going to Geant4.
Definition MCParticle.h:55
void setDescription(const std::string &description)
Sets the description of the module.
Definition Module.cc:214
Module()
Constructor.
Definition Module.cc:30
Class that bundles various TrackFitResults.
Definition Track.h:25
void addParam(const std::string &name, T &paramVariable, const std::string &description, const T &defaultValue)
Adds a new parameter to the module.
Definition Module.h:559
#define REG_MODULE(moduleName)
Register the given module (without 'Module' suffix) with the framework.
Definition Module.h:649
double sqrt(double a)
sqrt for double
Definition beamHelpers.h:28
std::vector< std::vector< double > > merge(const std::vector< std::vector< std::vector< double > > > &toMerge)
merge { vector<double> a, vector<double> b} into {a, b}
Definition tools.h:41
Abstract base class for different kinds of events.