Belle II Software  release-08-01-10
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 
17 using namespace Belle2;
18 
19 //-----------------------------------------------------------------
20 // Register the Module
21 //-----------------------------------------------------------------
22 
23 REG_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 
56 {
57 }
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.
102  chargedIdx += Const::ChargedStable::c_SetSize;
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 
131 {
132 
133  for (const auto& chargedPdgId : m_inputPdgIdSet) {
134 
135  const auto chargedHypo = Const::chargedStableSet.find(std::abs(chargedPdgId));
136 
137  // If merging particles and antiparticles for this hypo, no need to loop twice:
138  // fill one TTree for the '+' charged pdgId only.
139  if (m_mergeChargeFlagByHypo[chargedHypo] and chargedPdgId < 0) continue;
140 
141  // Get the idx of this pdgId in the Const::chargedStableSet
142  auto chargedIdx = chargedHypo.getIndex();
143 
144  if (chargedPdgId < 0) {
145  // Add offset to idx for antiparticles.
146  chargedIdx += Const::ChargedStable::c_SetSize;
147  }
148 
149  // Initialise branches to unphysical values.
150  m_p[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
151  m_pt[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
152  m_trkTheta[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
153  m_trkPhi[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
154  m_clusterTheta[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
155  m_clusterPhi[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
156  m_clusterReg[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
157  m_trackClusterMatch[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
158  m_logl_sig[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
159  m_logl_bkg[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
160  m_deltalogl_sig_bkg[chargedIdx] = std::numeric_limits<float>::quiet_NaN();
161  for (const auto& chargedStable : Const::chargedStableSet) {
162  m_pids_glob[chargedIdx][chargedStable.getIndex()] = std::numeric_limits<float>::quiet_NaN();
163  }
164 
165  for (const auto& particle : m_MCParticles) {
166 
167  if (!particle.hasStatus(MCParticle::c_PrimaryParticle)) continue; // Only check primaries.
168  if (particle.hasStatus(MCParticle::c_Initial)) continue; // Ignore initial particles.
169  if (particle.hasStatus(MCParticle::c_IsVirtual)) continue; // Ignore virtual particles.
170 
171  // Skip all particles expect for the one of interest.
172  // If merging particles and antiparticles for this pdgId, use abs so both charges are considered for the MCParticles.
173  if (m_mergeChargeFlagByHypo[chargedHypo]) {
174  // Charge-agnostic check.
175  if (std::abs(particle.getPDG()) != std::abs(chargedPdgId)) continue;
176  } else {
177  // Charge-dependent check.
178  if (particle.getPDG() != chargedPdgId) continue;
179  }
180 
181  // Get the matching track w/ max momentum.
182  int itrack(0);
183  int itrack_max(-1);
184  double p_max(-999.0);
185  for (const auto& track : particle.getRelationsFrom<Track>()) {
186  const auto fitRes = track.getTrackFitResultWithClosestMass(Const::pion);
187  if (!fitRes) continue;
188  if (fitRes->getMomentum().R() > p_max) {
189  p_max = fitRes->getMomentum().R();
190  itrack_max = itrack;
191  }
192  itrack++;
193  }
194  if (itrack_max < 0) continue; // Go to next particle if no track found.
195 
196  const auto track = particle.getRelationsFrom<Track>()[itrack_max];
197  // Very unlikely, but random failures due to missing ECLPidLikelihood have been observed
198  // Let's continue if this is a nullptr
199  const auto eclLikelihood = track->getRelated<ECLPidLikelihood>();
200  if (not eclLikelihood)
201  continue;
202  const auto fitRes = track->getTrackFitResultWithClosestMass(Const::pion);
203 
204  m_p[chargedIdx] = p_max;
205  m_pt[chargedIdx] = fitRes->get4Momentum().Pt();
206  m_trkTheta[chargedIdx] = fitRes->get4Momentum().Theta();
207  m_trkPhi[chargedIdx] = fitRes->get4Momentum().Phi();
208 
209  // Get the index of the ECL cluster matching this track.
210  int icluster_match(-1);
211  auto eclClusters = track->getRelationsTo<ECLCluster>();
212  for (unsigned int icluster(0); icluster < eclClusters.size(); ++icluster) {
213  const auto eclCluster = eclClusters[icluster];
214  if (!eclCluster->hasHypothesis(ECLCluster::EHypothesisBit::c_nPhotons)) continue;
215  if (!eclCluster->isTrack()) continue;
216  icluster_match = icluster;
217  break;
218  }
219  // If no cluster match, skip to next particle, but keep track of counter.
220  if (icluster_match < 0) {
221  m_trackClusterMatch[chargedIdx] = 0;
222  continue;
223  }
224 
225  const auto eclCluster = eclClusters[icluster_match];
226 
227  m_clusterTheta[chargedIdx] = eclCluster->getTheta();
228  m_clusterPhi[chargedIdx] = eclCluster->getPhi();
229  m_clusterReg[chargedIdx] = eclCluster->getDetectorRegion();
230 
231  m_trackClusterMatch[chargedIdx] = 1;
232 
233  // The "signal" likelihood corresponds to the current chargedPdgId.
234  const auto chargedStableSig = Const::chargedStableSet.find(std::abs(chargedPdgId));
235  // For deltaLogL, we do a binary comparison sig/bkg.
236  // If sig=pion, use bkg=kaon. Otherwise, bkg=pion.
237  const auto chargedStableBkg = (chargedStableSig == Const::pion) ? Const::kaon : Const::pion;
238 
239  double lh_sig = eclLikelihood->getLikelihood(chargedStableSig);
240  double lh_bkg = eclLikelihood->getLikelihood(chargedStableBkg);
241 
242  m_logl_sig[chargedIdx] = log(lh_sig);
243  m_logl_bkg[chargedIdx] = log(lh_bkg);
244  m_deltalogl_sig_bkg[chargedIdx] = log(lh_bkg) - log(lh_sig);
245 
246  // For the current charged particle candidate, store the global likelihood ratio for all hypotheses.
247  double lh_all(0);
248  for (const auto& chargedStable : Const::chargedStableSet) {
249  lh_all += eclLikelihood->getLikelihood(chargedStable);
250  }
251  for (const auto& chargedStable : Const::chargedStableSet) {
252  m_pids_glob[chargedIdx][chargedStable.getIndex()] = eclLikelihood->getLikelihood(chargedStable) / lh_all;
253  }
254 
255  }
256 
257  m_tree[chargedIdx]->Fill();
258 
259  }
260 }
261 
263 {
264 }
265 
267 {
268 
269  for (const auto& chargedPdgId : m_inputPdgIdSet) {
270 
271  // Define the charged stable particle ("sample") corresponding to the current pdgId.
272  const auto chargedStableSample = Const::chargedStableSet.find(std::abs(chargedPdgId));
273 
274  const auto mergeCharge = m_mergeChargeFlagByHypo[chargedStableSample];
275 
276  // If partcile/antiparticle merging is active for this hypo, the results are stored for the '+' particle only.
277  if (mergeCharge && chargedPdgId <= 0) continue;
278 
279  // Extract the sign of the charge.
280  const auto chargeSign = static_cast<int>(chargedPdgId / std::abs(chargedPdgId));
281 
282  // What we call "signal" is equal to the sample under consideration.
283  const auto chargedStableSig = chargedStableSample;
284 
285  // What we call "background" depends on the current "signal" particle hypothesis.
286  const auto chargedStableBkg = (chargedStableSig == Const::pion) ? Const::kaon : Const::pion;
287 
288  // Get the idx of this sample's pdgId to retrieve the correct TTree and output TFile.
289  // Remember to add offset for antiparticles.
290  auto chargedSampleIdx = (chargeSign > 0) ? chargedStableSig.getIndex() : chargedStableSig.getIndex() +
292 
293  m_outputFile[chargedSampleIdx]->cd();
294 
295  auto pdgIdDesc = (mergeCharge) ? std::to_string(chargedPdgId) + " and -" + std::to_string(chargedPdgId) : std::to_string(
296  chargedPdgId);
297 
298  // Add summary description of validation file content.
299  TNamed("Description", TString::Format("ECL Charged PID control plots for charged stable particles/antiparticles ; Sample PDG = %s",
300  pdgIdDesc.c_str()).Data()).Write();
301 
302  // Dump plots of PID variables.
303  dumpPIDVars(m_tree[chargedSampleIdx], chargedStableSig, chargeSign, chargedStableBkg, mergeCharge);
304  // Dump plots of PID "signal" efficiency for this "sample".
305  dumpPIDEfficiencyFakeRate(m_tree[chargedSampleIdx], chargedStableSample, chargeSign, chargedStableSig, mergeCharge);
306  // For pions, dump also the pi->lep fake rate.
307  if (chargedStableSample == Const::pion) {
308  dumpPIDEfficiencyFakeRate(m_tree[chargedSampleIdx], chargedStableSample, chargeSign, Const::electron, mergeCharge);
309  dumpPIDEfficiencyFakeRate(m_tree[chargedSampleIdx], chargedStableSample, chargeSign, Const::muon, mergeCharge);
310  }
311  // Dump plots of matching efficiency for this "sample".
312  dumpTrkClusMatchingEfficiency(m_tree[chargedSampleIdx], chargedStableSample, chargeSign, mergeCharge);
313 
314  // Write the TTree to file if requested.
315  if (m_saveValidationTree) {
316  m_tree[chargedSampleIdx]->Write();
317  }
318 
319  m_outputFile[chargedSampleIdx]->Close();
320 
321  }
322 }
323 
325  const int sigCharge, const Const::ChargedStable& bkgHypo, bool mergeSigCharge)
326 {
327 
328  // Get the idx and pdgId of the input sample particle.
329  // This corresponds by construction to the "signal" hypothesis for the likelihood and DeltaLogL.
330  const auto sigHypoIdx = sigHypo.getIndex();
331  const auto sigHypoPdgId = sigHypo.getPDGCode();
332 
333  // Get the pdgId of the "background" hypothesis to test for DeltaLogL.
334  const auto bkgHypoPdgId = bkgHypo.getPDGCode();
335 
336  // Access the "signal" hypothesis's PID component in the sample's
337  // TTree vector branch of global PID values via the idx.
338  TString pidSigBranch = TString::Format("pids_glob[%i]", sigHypoIdx);
339 
340  // Histogram of global PID distribution for the sample particle's signal hypo.
341  TString h_pid_name = TString::Format("h_pid_sig_%i", sigHypoPdgId);
342  TH1F* h_pid = new TH1F(h_pid_name.Data(), h_pid_name.Data(), 50, -0.5, 1.2);
343  h_pid->GetXaxis()->SetTitle(TString::Format("Likelihood ratio (%i/ALL) (ECL)", sigHypoPdgId).Data());
344 
345  // Histogram of deltalogl.
346  TString h_deltalogl_name = TString::Format("h_deltalogl_bkg_%i_sig_%i", bkgHypoPdgId, sigHypoPdgId);
347  double deltalogl_min = -20.0;
348  double deltalogl_max = 20.0;
349  TH1F* h_deltalogl = new TH1F(h_deltalogl_name.Data(), h_deltalogl_name.Data(), 40, deltalogl_min, deltalogl_max);
350  h_deltalogl->GetXaxis()->SetTitle(TString::Format("#Deltaln(L) (%i/%i) (ECL)", bkgHypoPdgId, sigHypoPdgId).Data());
351 
352  // Histogram of track-cluster match flag.
353  TString h_trkclusmatch_name = TString::Format("h_trkclusmatch_sig_%i", sigHypoPdgId);
354  TH1F* h_trkclusmatch = new TH1F(h_trkclusmatch_name.Data(), h_trkclusmatch_name.Data(), 4, -1.5, 2.5);
355  h_trkclusmatch->GetXaxis()->SetTitle(TString::Format("Track-ECLCluster match (%i)", sigHypoPdgId).Data());
356 
357  // Dump histos from TTree.
358  sampleTree->Project(h_pid_name.Data(), pidSigBranch.Data());
359  sampleTree->Project(h_deltalogl_name.Data(), "deltalogl_sig_bkg");
360  sampleTree->Project(h_trkclusmatch_name.Data(), "trackClusterMatch");
361 
362  // Make sure the plots show the u/oflow.
363  paintUnderOverflow(h_pid);
364  paintUnderOverflow(h_deltalogl);
365  paintUnderOverflow(h_trkclusmatch);
366 
367  h_pid->SetOption("HIST");
368  h_deltalogl->SetOption("HIST");
369  h_trkclusmatch->SetOption("HIST");
370 
371  // MetaOptions string.
372  std::string metaopts("pvalue-warn=0.1,pvalue-error=0.01");
373  std::string shifteropt("");
374  // Electron plots should be visible to the shifter by default.
375  if (sigHypo == Const::electron) {
376  shifteropt = "shifter,";
377  }
378 
379  auto pdgIdDesc = (!mergeSigCharge) ? std::to_string(sigHypoPdgId * sigCharge) : std::to_string(
380  sigHypoPdgId) + " and -" + std::to_string(sigHypoPdgId);
381 
382  // Add histogram info.
383  h_pid->GetListOfFunctions()->Add(new TNamed("Description",
384  TString::Format("Sample PDG = %s ; ECL global PID(%i) distribution. U/O flow is added to first (last) bin.",
385  pdgIdDesc.c_str(),
386  sigHypoPdgId).Data()));
387  h_pid->GetListOfFunctions()->Add(new TNamed("Check",
388  "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."));
389  h_pid->GetListOfFunctions()->Add(new TNamed("Contact", "Marco Milesi. marco.milesi@desy.de"));
390  h_pid->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
391 
392  h_deltalogl->GetListOfFunctions()->Add(new TNamed("Description",
393  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.",
394  pdgIdDesc.c_str(),
395  bkgHypoPdgId,
396  sigHypoPdgId).Data()));
397  h_deltalogl->GetListOfFunctions()->Add(new TNamed("Check",
398  "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)."));
399  h_deltalogl->GetListOfFunctions()->Add(new TNamed("Contact", "Marco Milesi. marco.milesi@desy.de"));
400  h_deltalogl->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
401 
402  h_trkclusmatch->GetListOfFunctions()->Add(new TNamed("Description",
403  TString::Format("Sample PDG = %s ; Track-ECLCluster match flag distribution.",
404  pdgIdDesc.c_str()).Data()));
405  h_trkclusmatch->GetListOfFunctions()->Add(new TNamed("Check",
406  "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."));
407  h_trkclusmatch->GetListOfFunctions()->Add(new TNamed("Contact", "Frank Meier. frank.meier@desy.de"));
408  h_trkclusmatch->GetListOfFunctions()->Add(new TNamed("MetaOptions", metaopts.c_str()));
409 
410  h_pid->Write();
411  h_deltalogl->Write();
412  h_trkclusmatch->Write();
413 
414  delete h_pid;
415  delete h_deltalogl;
416  delete h_trkclusmatch;
417 
418 }
419 
420 
422  const int sampleCharge, const Const::ChargedStable& sigHypo, bool mergeSampleCharge)
423 {
424 
425  // The ratio type: EFFICIENCY || FAKE RATE
426  const std::string ratioType = (sampleHypo == sigHypo) ? "Efficiency" : "FakeRate";
427 
428  // Get the *signed* pdgId of the input sample particle.
429  const int sampleHypoPdgId = sampleHypo.getPDGCode() * sampleCharge;
430 
431  // Get the idx and pdgId of the "signal" hypothesis to test.
432  const auto sigHypoIdx = sigHypo.getIndex();
433  const auto sigHypoPdgId = sigHypo.getPDGCode();
434 
435  // Access the "signal" hypothesis's PID component in the sample's
436  // TTree vector branch of global PID values via the idx.
437  TString pidSigCut = TString::Format("pids_glob[%i] > %f", sigHypoIdx, c_PID);
438 
439  // Histograms of p, clusterReg, clusterPhi... for "pass" (N, numerator) and "all" (D, denominator) events.
440  TString h_p_N_name = TString::Format("h_p_N_%i", sigHypoPdgId);
441  TString h_p_D_name = TString::Format("h_p_D_%i", sigHypoPdgId);
442  TH1F* h_p_N = new TH1F(h_p_N_name.Data(), "h_p_N", 10, 0.0, 5.0);
443  TH1F* h_p_D = new TH1F(h_p_D_name.Data(), "h_p_D", 10, 0.0, 5.0);
444 
445  TString h_th_N_name = TString::Format("h_th_N_%i", sigHypoPdgId);
446  TString h_th_D_name = TString::Format("h_th_D_%i", sigHypoPdgId);
447  TH1F* h_th_N = new TH1F(h_th_N_name.Data(), "h_th_N", m_th_binedges.size() - 1, m_th_binedges.data());
448  TH1F* h_th_D = new TH1F(h_th_D_name.Data(), "h_th_D", m_th_binedges.size() - 1, m_th_binedges.data());
449 
450  TString h_eclreg_N_name = TString::Format("h_eclreg_N_%i", sigHypoPdgId);
451  TString h_eclreg_D_name = TString::Format("h_eclreg_D_%i", sigHypoPdgId);
452  TH1F* h_eclreg_N = new TH1F(h_eclreg_N_name.Data(), "h_eclreg_N", 5, -0.5, 4.5);
453  TH1F* h_eclreg_D = new TH1F(h_eclreg_D_name.Data(), "h_eclreg_D", 5, -0.5, 4.5);
454 
455  TString h_phi_N_name = TString::Format("h_phi_N_%i", sigHypoPdgId);
456  TString h_phi_D_name = TString::Format("h_phi_D_%i", sigHypoPdgId);
457  TH1F* h_phi_N = new TH1F(h_phi_N_name.Data(), "h_phi_N", 5, -3.14159, 3.14159);
458  TH1F* h_phi_D = new TH1F(h_phi_D_name.Data(), "h_phi_D", 5, -3.14159, 3.14159);
459 
460  // Fill the histograms from the sample's TTree.
461 
462  sampleTree->Project(h_p_N_name.Data(), "p", pidSigCut.Data());
463  sampleTree->Project(h_p_D_name.Data(), "p");
464 
465  sampleTree->Project(h_th_N_name.Data(), "clusterTheta", pidSigCut.Data());
466  sampleTree->Project(h_th_D_name.Data(), "clusterTheta");
467 
468  sampleTree->Project(h_eclreg_N_name.Data(), "clusterReg", pidSigCut.Data());
469  sampleTree->Project(h_eclreg_D_name.Data(), "clusterReg");
470  paintUnderOverflow(h_eclreg_N);
471  paintUnderOverflow(h_eclreg_D);
472 
473  sampleTree->Project(h_phi_N_name.Data(), "clusterPhi", pidSigCut.Data());
474  sampleTree->Project(h_phi_D_name.Data(), "clusterPhi");
475 
476  // Compute the efficiency/fake rate.
477 
478  TString pid_glob_ratio_p_name = TString::Format("pid_glob_%i_%s__VS_p", sigHypoPdgId, ratioType.c_str());
479  TString pid_glob_ratio_th_name = TString::Format("pid_glob_%i_%s__VS_th", sigHypoPdgId, ratioType.c_str());
480  TString pid_glob_ratio_eclreg_name = TString::Format("pid_glob_%i_%s__VS_eclreg", sigHypoPdgId, ratioType.c_str());
481  TString pid_glob_ratio_phi_name = TString::Format("pid_glob_%i_%s__VS_phi", sigHypoPdgId, ratioType.c_str());
482 
483  // MetaOptions string.
484  std::string metaopts("pvalue-warn=0.01,pvalue-error=0.001,nostats");
485  std::string shifteropt("");
486  // Electron plots should be visible to the shifter by default.
487  if (sampleHypo == Const::electron || sigHypo == Const::electron) {
488  shifteropt = "shifter,";
489  }
490 
491  auto pdgIdDesc = (!mergeSampleCharge) ? std::to_string(sampleHypoPdgId) : std::to_string(std::abs(
492  sampleHypoPdgId)) + " and -" + std::to_string(std::abs(sampleHypoPdgId));
493 
494  if (TEfficiency::CheckConsistency(*h_p_N, *h_p_D)) {
495 
496  TEfficiency* t_pid_glob_ratio_p = new TEfficiency(*h_p_N, *h_p_D);
497  t_pid_glob_ratio_p->SetName(pid_glob_ratio_p_name.Data());
498  t_pid_glob_ratio_p->SetTitle(TString::Format("%s;p [GeV/c];#varepsilon/f", pid_glob_ratio_p_name.Data()).Data());
499 
500  t_pid_glob_ratio_p->SetConfidenceLevel(0.683);
501  t_pid_glob_ratio_p->SetStatisticOption(TEfficiency::kBUniform);
502  t_pid_glob_ratio_p->SetPosteriorMode();
503 
504  t_pid_glob_ratio_p->GetListOfFunctions()->Add(new TNamed("Description",
505  TString::Format("Sample PDG = %s ; %s of ECL global PID(%i) > %.2f as a function of $p_{trk}$.",
506  pdgIdDesc.c_str(),
507  ratioType.c_str(),
508  sigHypoPdgId,
509  c_PID).Data()));
510  t_pid_glob_ratio_p->GetListOfFunctions()->Add(new TNamed("Check",
511  "Shape should be consistent. Obviously, check for decreasing efficiency / increasing fake rate."));
512  t_pid_glob_ratio_p->GetListOfFunctions()->Add(new TNamed("Contact", "Marco Milesi. marco.milesi@desy.de"));
513  t_pid_glob_ratio_p->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
514 
515  t_pid_glob_ratio_p->Write();
516 
517  delete t_pid_glob_ratio_p;
518 
519  }
520  if (TEfficiency::CheckConsistency(*h_th_N, *h_th_D)) {
521 
522  TEfficiency* t_pid_glob_ratio_th = new TEfficiency(*h_th_N, *h_th_D);
523  t_pid_glob_ratio_th->SetName(pid_glob_ratio_th_name.Data());
524  t_pid_glob_ratio_th->SetTitle(TString::Format("%s;#theta_{cluster} [rad];#varepsilon/f", pid_glob_ratio_th_name.Data()).Data());
525 
526  t_pid_glob_ratio_th->SetConfidenceLevel(0.683);
527  t_pid_glob_ratio_th->SetStatisticOption(TEfficiency::kBUniform);
528  t_pid_glob_ratio_th->SetPosteriorMode();
529 
530  t_pid_glob_ratio_th->GetListOfFunctions()->Add(new TNamed("Description",
531  TString::Format("Sample PDG = %s ; %s of ECL global PID(%i) > %.2f as a function of $\\theta_{cluster}$.",
532  pdgIdDesc.c_str(),
533  ratioType.c_str(),
534  sigHypoPdgId,
535  c_PID).Data()));
536  t_pid_glob_ratio_th->GetListOfFunctions()->Add(new TNamed("Check",
537  "Shape should be consistent. Obviously, check for decreasing efficiency / increasing fake rate."));
538  t_pid_glob_ratio_th->GetListOfFunctions()->Add(new TNamed("Contact", "Marco Milesi. marco.milesi@desy.de"));
539  t_pid_glob_ratio_th->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
540 
541  t_pid_glob_ratio_th->Write();
542 
543  delete t_pid_glob_ratio_th;
544  }
545  if (TEfficiency::CheckConsistency(*h_eclreg_N, *h_eclreg_D)) {
546 
547  TEfficiency* t_pid_glob_ratio_eclreg = new TEfficiency(*h_eclreg_N, *h_eclreg_D);
548  t_pid_glob_ratio_eclreg->SetName(pid_glob_ratio_eclreg_name.Data());
549  t_pid_glob_ratio_eclreg->SetTitle(TString::Format("%s;ECL Region;#varepsilon/f", pid_glob_ratio_eclreg_name.Data()).Data());
550 
551  t_pid_glob_ratio_eclreg->SetConfidenceLevel(0.683);
552  t_pid_glob_ratio_eclreg->SetStatisticOption(TEfficiency::kBUniform);
553  t_pid_glob_ratio_eclreg->SetPosteriorMode();
554 
555  t_pid_glob_ratio_eclreg->GetListOfFunctions()->Add(new TNamed("Description",
556  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).",
557  pdgIdDesc.c_str(),
558  ratioType.c_str(),
559  sigHypoPdgId,
560  c_PID).Data()));
561  t_pid_glob_ratio_eclreg->GetListOfFunctions()->Add(new TNamed("Check",
562  "Shape should be consistent. Obviously, check for decreasing efficiency / increasing fake rate."));
563  t_pid_glob_ratio_eclreg->GetListOfFunctions()->Add(new TNamed("Contact", "Marco Milesi. marco.milesi@desy.de"));
564  t_pid_glob_ratio_eclreg->GetListOfFunctions()->Add(new TNamed("MetaOptions", metaopts.c_str()));
565 
566  t_pid_glob_ratio_eclreg->Write();
567 
568  delete t_pid_glob_ratio_eclreg;
569 
570  }
571  if (TEfficiency::CheckConsistency(*h_phi_N, *h_phi_D)) {
572 
573  TEfficiency* t_pid_glob_ratio_phi = new TEfficiency(*h_phi_N, *h_phi_D);
574  t_pid_glob_ratio_phi->SetName(pid_glob_ratio_phi_name.Data());
575  t_pid_glob_ratio_phi->SetTitle(TString::Format("%s;#phi_{cluster} [rad];#varepsilon/f", pid_glob_ratio_phi_name.Data()).Data());
576 
577  t_pid_glob_ratio_phi->SetConfidenceLevel(0.683);
578  t_pid_glob_ratio_phi->SetStatisticOption(TEfficiency::kBUniform);
579  t_pid_glob_ratio_phi->SetPosteriorMode();
580 
581  t_pid_glob_ratio_phi->GetListOfFunctions()->Add(new TNamed("Description",
582  TString::Format("Sample PDG = %s ; %s of ECL global PID(%i) > %.2f as a function of $\\phi_{cluster}$.",
583  pdgIdDesc.c_str(),
584  ratioType.c_str(),
585  sigHypoPdgId,
586  c_PID).Data()));
587  t_pid_glob_ratio_phi->GetListOfFunctions()->Add(new TNamed("Check",
588  "Shape should be consistent. Obviously, check for decreasing efficiency / increasing fake rate."));
589  t_pid_glob_ratio_phi->GetListOfFunctions()->Add(new TNamed("Contact", "Marco Milesi. marco.milesi@desy.de"));
590  t_pid_glob_ratio_phi->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
591 
592  t_pid_glob_ratio_phi->Write();
593 
594  delete t_pid_glob_ratio_phi;
595  }
596 
597  delete h_p_N;
598  delete h_p_D;
599  delete h_th_N;
600  delete h_th_D;
601  delete h_eclreg_N;
602  delete h_eclreg_D;
603  delete h_phi_N;
604  delete h_phi_D;
605 
606 }
607 
608 
610  const int sampleCharge, bool mergeSampleCharge)
611 {
612 
613  // Get the (unsigned) pdgId of the input sample particle.
614  const auto sampleHypoPdgId = sampleHypo.getPDGCode();
615 
616  // Histograms of pt, clusterTheta, clusterPhi... for "pass" (N, numerator) and "all" (D, denominator) events.
617  TString h_pt_N_name = TString::Format("h_pt_N_%i", sampleHypoPdgId);
618  TString h_pt_D_name = TString::Format("h_pt_D_%i", sampleHypoPdgId);
619  TH1F* h_pt_N = new TH1F(h_pt_N_name.Data(), "h_pt_N", 10, 0.0, 5.0);
620  TH1F* h_pt_D = new TH1F(h_pt_D_name.Data(), "h_pt_D", 10, 0.0, 5.0);
621 
622  TString h_th_N_name = TString::Format("h_th_N_%i", sampleHypoPdgId);
623  TString h_th_D_name = TString::Format("h_th_D_%i", sampleHypoPdgId);
624  TH1F* h_th_N = new TH1F(h_th_N_name.Data(), "h_th_N", m_th_binedges.size() - 1, m_th_binedges.data());
625  TH1F* h_th_D = new TH1F(h_th_D_name.Data(), "h_th_D", m_th_binedges.size() - 1, m_th_binedges.data());
626 
627  TString h_phi_N_name = TString::Format("h_phi_N_%i", sampleHypoPdgId);
628  TString h_phi_D_name = TString::Format("h_phi_D_%i", sampleHypoPdgId);
629  TH1F* h_phi_N = new TH1F(h_phi_N_name.Data(), "h_phi_N", 5, -3.14159, 3.14159);
630  TH1F* h_phi_D = new TH1F(h_phi_D_name.Data(), "h_phi_D", 5, -3.14159, 3.14159);
631 
632  TString match_cut_N("trackClusterMatch == 1");
633  TString match_cut_D("trackClusterMatch >= 0");
634 
635  // Fill the histograms from the sample's TTree.
636 
637  tree->Project(h_pt_N_name.Data(), "pt", match_cut_N.Data());
638  tree->Project(h_pt_D_name.Data(), "pt", match_cut_D.Data());
639 
640  tree->Project(h_th_N_name.Data(), "trkTheta", match_cut_N.Data());
641  tree->Project(h_th_D_name.Data(), "trkTheta", match_cut_D.Data());
642 
643  tree->Project(h_phi_N_name.Data(), "trkPhi", match_cut_N.Data());
644  tree->Project(h_phi_D_name.Data(), "trkPhi", match_cut_D.Data());
645 
646  // Compute the efficiency.
647 
648  TString match_eff_pt_name = TString::Format("trkclusmatch_%i_Efficiency__VS_pt", sampleHypoPdgId);
649  TString match_eff_th_name = TString::Format("trkclusmatch_%i_Efficiency__VS_th", sampleHypoPdgId);
650  TString match_eff_phi_name = TString::Format("trkclusmatch_%i_Efficiency__VS_phi", sampleHypoPdgId);
651 
652  // MetaOptions string.
653  std::string metaopts("pvalue-warn=0.01,pvalue-error=0.001,nostats");
654  std::string shifteropt("");
655  // Electron plots should be visible to the shifter by default.
656  if (sampleHypo == Const::electron) {
657  shifteropt = "shifter,";
658  }
659 
660  auto pdgIdDesc = (!mergeSampleCharge) ? std::to_string(sampleHypoPdgId * sampleCharge) : std::to_string(
661  sampleHypoPdgId) + " and -" + std::to_string(sampleHypoPdgId);
662 
663  if (TEfficiency::CheckConsistency(*h_pt_N, *h_pt_D)) {
664 
665  TEfficiency* t_match_eff_pt = new TEfficiency(*h_pt_N, *h_pt_D);
666  t_match_eff_pt->SetName(match_eff_pt_name.Data());
667  t_match_eff_pt->SetTitle(TString::Format("%s;p_{T}^{trk} [GeV/c];#varepsilon", match_eff_pt_name.Data()).Data());
668  t_match_eff_pt->SetTitle(match_eff_pt_name.Data());
669 
670  t_match_eff_pt->SetConfidenceLevel(0.683);
671  t_match_eff_pt->SetStatisticOption(TEfficiency::kBUniform);
672  t_match_eff_pt->SetPosteriorMode();
673 
674  t_match_eff_pt->GetListOfFunctions()->Add(new TNamed("Description",
675  TString::Format("Sample PDG = %s ; Efficiency of track-ECL-cluster matching as a function of $p_{T}^{trk}$.",
676  pdgIdDesc.c_str()).Data()));
677  t_match_eff_pt->GetListOfFunctions()->Add(new TNamed("Check",
678  "Shape should be consistent. Obviously, check for decreasing efficiency."));
679  t_match_eff_pt->GetListOfFunctions()->Add(new TNamed("Contact", "Frank Meier. frank.meier@desy.de"));
680  t_match_eff_pt->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
681 
682  t_match_eff_pt->Write();
683 
684  delete t_match_eff_pt;
685 
686  }
687  if (TEfficiency::CheckConsistency(*h_th_N, *h_th_D)) {
688 
689  TEfficiency* t_match_eff_th = new TEfficiency(*h_th_N, *h_th_D);
690  t_match_eff_th->SetName(match_eff_th_name.Data());
691  t_match_eff_th->SetTitle(TString::Format("%s;#theta_{trk} [rad];#varepsilon", match_eff_th_name.Data()).Data());
692  t_match_eff_th->SetTitle(match_eff_th_name.Data());
693 
694  t_match_eff_th->SetConfidenceLevel(0.683);
695  t_match_eff_th->SetStatisticOption(TEfficiency::kBUniform);
696  t_match_eff_th->SetPosteriorMode();
697 
698  t_match_eff_th->GetListOfFunctions()->Add(new TNamed("Description",
699  TString::Format("Sample PDG = %s ; Efficiency of track-ECL-cluster matching as a function of $\\theta_{trk}$.",
700  pdgIdDesc.c_str()).Data()));
701  t_match_eff_th->GetListOfFunctions()->Add(new TNamed("Check",
702  "Shape should be consistent. Obviously, check for decreasing efficiency."));
703  t_match_eff_th->GetListOfFunctions()->Add(new TNamed("Contact", "Frank Meier. frank.meier@desy.de"));
704  t_match_eff_th->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
705 
706  t_match_eff_th->Write();
707 
708  delete t_match_eff_th;
709 
710  }
711  if (TEfficiency::CheckConsistency(*h_phi_N, *h_phi_D)) {
712 
713  TEfficiency* t_match_eff_phi = new TEfficiency(*h_phi_N, *h_phi_D);
714  t_match_eff_phi->SetName(match_eff_phi_name.Data());
715  t_match_eff_phi->SetTitle(TString::Format("%s;#phi_{trk} [rad];#varepsilon", match_eff_phi_name.Data()).Data());
716 
717  t_match_eff_phi->SetConfidenceLevel(0.683);
718  t_match_eff_phi->SetStatisticOption(TEfficiency::kBUniform);
719  t_match_eff_phi->SetPosteriorMode();
720 
721  t_match_eff_phi->GetListOfFunctions()->Add(new TNamed("Description",
722  TString::Format("Sample PDG = %s ; Efficiency of track-ECL-cluster matching as a function of $\\phi_{trk}$.",
723  pdgIdDesc.c_str()).Data()));
724  t_match_eff_phi->GetListOfFunctions()->Add(new TNamed("Check",
725  "Shape should be consistent. Obviously, check for decreasing efficiency."));
726  t_match_eff_phi->GetListOfFunctions()->Add(new TNamed("Contact", "Frank Meier. frank.meier@desy.de"));
727  t_match_eff_phi->GetListOfFunctions()->Add(new TNamed("MetaOptions", (shifteropt + metaopts).c_str()));
728 
729  t_match_eff_phi->Write();
730 
731  delete t_match_eff_phi;
732  }
733 
734  delete h_pt_N;
735  delete h_pt_D;
736  delete h_th_N;
737  delete h_th_D;
738  delete h_phi_N;
739  delete h_phi_D;
740 
741 }
742 
743 
745 {
746 
747  auto nentries = h->GetEntries();
748  auto nbins_vis = h->GetNbinsX();
749 
750  // Get the content and error of first/last visible bin.
751  float bin_vis_first = h->GetBinContent(1);
752  float bin_vis_last = h->GetBinContent(nbins_vis);
753  float bin_vis_first_err = h->GetBinError(1);
754  float bin_vis_last_err = h->GetBinError(nbins_vis);
755 
756  // Get the content and error of u/oflow bins.
757  float bin_uflow = h->GetBinContent(0);
758  float bin_oflow = h->GetBinContent(nbins_vis + 1);
759  float bin_uflow_err = h->GetBinError(0);
760  float bin_oflow_err = h->GetBinError(nbins_vis + 1);
761 
762  // Reset first/last visible bins to include u/oflow.
763  h->SetBinContent(1, bin_vis_first + bin_uflow);
764  h->SetBinError(1, sqrt(bin_vis_first_err * bin_vis_first_err + bin_uflow_err * bin_uflow_err));
765  h->SetBinContent(nbins_vis, bin_vis_last + bin_oflow);
766  h->SetBinError(nbins_vis, sqrt(bin_vis_last_err * bin_vis_last_err + bin_oflow_err * bin_oflow_err));
767 
768  // Reset total entries to the original value.
769  h->SetEntries(nentries);
770 
771 }
Provides a type-safe way to pass members of the chargedStableSet set.
Definition: Const.h:580
static const unsigned int c_SetSize
Number of elements (for use in array bounds etc.)
Definition: Const.h:606
const ParticleType & find(int pdg) const
Returns particle in set with given PDG code, or invalidParticle if not found.
Definition: Const.h:562
int getPDGCode() const
PDG code.
Definition: Const.h:464
int getIndex() const
This particle's index in the associated set.
Definition: Const.h:452
static const ChargedStable muon
muon particle
Definition: Const.h:651
static const ParticleSet chargedStableSet
set of charged stable particles
Definition: Const.h:609
static const ChargedStable pion
charged pion particle
Definition: Const.h:652
static const ChargedStable kaon
charged kaon particle
Definition: Const.h:653
static const ChargedStable electron
electron particle
Definition: Const.h:650
std::vector< float > m_trackClusterMatch
Flag for track-cluster matching condition.
bool isValidChargedPdg(const int pdg) const
Check if the input pdgId is that of a valid charged stable particle.
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.
virtual void endRun() override
Called once when a run ends.
std::vector< float > m_deltalogl_sig_bkg
Delta Log-likelihood "signal" vs.
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...
bool m_saveValidationTree
Save the TTree in the output file alongside the histograms.
std::vector< float > m_logl_bkg
Log-likelihood for the "background" particle hypothesis.
virtual void beginRun() override
Called once before a new run begins.
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 ...
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::string m_outputFileName
Base name of the output ROOT::TFile.
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)
Container for likelihoods with ECL PID (ECLChargedPIDModule)
@ 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
Base class for Modules.
Definition: Module.h:72
void setDescription(const std::string &description)
Sets the description of the module.
Definition: Module.cc:214
Class that bundles various TrackFitResults.
Definition: Track.h:25
REG_MODULE(arichBtest)
Register the Module.
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:560
double sqrt(double a)
sqrt for double
Definition: beamHelpers.h:28
std::vector< std::vector< double > > merge(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.