Belle II Software  release-06-01-15
ECLChargedPIDModule.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 
9 #include <math.h>
10 #include <algorithm>
11 #include "TMath.h"
12 
13 #include <ecl/modules/eclChargedPID/ECLChargedPIDModule.h>
14 
15 using namespace Belle2;
16 
17 REG_MODULE(ECLChargedPID)
18 
19 
21 {
22  setDescription("The module implements charged particle identification using ECL-related observables. For each Track matched with a suitable ECLShower, likelihoods for each particle hypothesis are obtained from pdfs stored in a conditions database payload, and then get stored in an ECLPidLikelihood object. The dimensionality of the likelihood depends on how many variables are stored in the payload. The baseline method could be a simple univariate likelihood based on E/p PDFs, but it could be extended to include more ECL quantitites (e.g. shower shape variables, w/ proper decorrelation).");
23 
24  setPropertyFlags(c_ParallelProcessingCertified);
25 
26  addParam("applyClusterTimingSel",
27  m_applyClusterTimingSel,
28  "Set true if you want to apply a abs(clusterTiming)/clusterTimingError<1 cut on clusters. This cut is optimised to achieve 99% timing efficiency for true photons from the IP.",
29  bool(false));
30 }
31 
32 
34 
35 
37 {
38  m_eventMetaData.isRequired();
39 
40  m_eclPidLikelihoods.registerInDataStore();
41  m_tracks.registerRelationTo(m_eclPidLikelihoods);
42 }
43 
44 
46 {
47  if (!m_pdfs) { B2FATAL("No ECL charged PID PDFs payload found in database!"); }
48 }
49 
50 
52 {
53  m_pdfs.addCallback([this]() { checkPdfsDB(); });
54  checkPdfsDB();
55 }
56 
57 
59 {
60 
61  for (const auto& track : m_tracks) {
62 
63  // Don't forget to clear variable map before a track gets processed!
64  m_variables.clear();
65 
66  // Load the pion fit hypothesis or the hypothesis which is the closest in mass to a pion
67  // (the tracking will not always successfully fit with a pion hypothesis).
68  const TrackFitResult* fitRes = track.getTrackFitResultWithClosestMass(Const::pion);
69  if (fitRes == nullptr) continue;
70  const auto relShowers = track.getRelationsTo<ECLShower>();
71  if (relShowers.size() == 0) continue;
72 
73  const double p = fitRes->getMomentum().Mag();
74  const double theta = fitRes->getMomentum().Theta();
75  const auto charge = fitRes->getChargeSign();
76 
77  double shEnergy(0.0), maxEnergy(0.0);
78 
79  const ECLShower* mostEnergeticShower = nullptr;
80 
81  for (const auto& eclShower : relShowers) {
82 
83  if (eclShower.getHypothesisId() != ECLShower::c_nPhotons) continue;
85  if (abs(eclShower.getTime()) > eclShower.getDeltaTime99()) continue;
86  }
87 
88  shEnergy = eclShower.getEnergy();
89  if (shEnergy > maxEnergy) {
90  maxEnergy = shEnergy;
91  mostEnergeticShower = &eclShower;
92  }
93 
94  }
95 
96  double showerTheta = (mostEnergeticShower) ? mostEnergeticShower->getTheta() : -999.0;
97  int showerReg = (mostEnergeticShower) ? mostEnergeticShower->getDetectorRegion() : -1;
98 
99  // These are the variables that can be used to extract PDF templates for the likelihood / for the MVA training.
100  m_variables[ECLChargedPidPDFs::InputVar::c_E1E9] = (mostEnergeticShower) ? mostEnergeticShower->getE1oE9() : -1.0;
101  m_variables[ECLChargedPidPDFs::InputVar::c_E9E21] = (mostEnergeticShower) ? mostEnergeticShower->getE9oE21() : -1.0;
102  m_variables[ECLChargedPidPDFs::InputVar::c_E] = (mostEnergeticShower) ? maxEnergy : -1.0;
103  m_variables[ECLChargedPidPDFs::InputVar::c_EoP] = (mostEnergeticShower) ? maxEnergy / p : -1.0;
104  m_variables[ECLChargedPidPDFs::InputVar::c_Z40] = (mostEnergeticShower) ? mostEnergeticShower->getAbsZernike40() : -999.0;
105  m_variables[ECLChargedPidPDFs::InputVar::c_Z51] = (mostEnergeticShower) ? mostEnergeticShower->getAbsZernike51() : -999.0;
106  m_variables[ECLChargedPidPDFs::InputVar::c_ZMVA] = (mostEnergeticShower) ? mostEnergeticShower->getZernikeMVA() : -999.0;
107  m_variables[ECLChargedPidPDFs::InputVar::c_PSDMVA] = (mostEnergeticShower) ? mostEnergeticShower->getPulseShapeDiscriminationMVA() :
108  -999.0;
109  m_variables[ECLChargedPidPDFs::InputVar::c_DeltaL] = (mostEnergeticShower) ? mostEnergeticShower->getTrkDepth() : -1.0;
110  m_variables[ECLChargedPidPDFs::InputVar::c_LAT] = (mostEnergeticShower) ? mostEnergeticShower->getLateralEnergy() : -999.0;
111 
112  B2DEBUG(20, "EVENT: " << m_eventMetaData->getEvent());
113  B2DEBUG(20, "-------------------------------");
114  B2DEBUG(20, "TRACK properties:");
115  B2DEBUG(20, "p = " << p << " [GeV/c]");
116  B2DEBUG(30, "theta = " << theta << " [rad]");
117  B2DEBUG(20, "charge = " << charge);
118  B2DEBUG(20, "-------------------------------");
119  B2DEBUG(20, "SHOWER properties:");
120  B2DEBUG(20, "showerTheta = " << showerTheta << " [rad], showerReg = " << showerReg);
121  for (const auto& [varid, var] : m_variables) {
122  B2DEBUG(30, "varid: " << static_cast<unsigned int>(varid) << " = " << var);
123  }
124  B2DEBUG(20, "-------------------------------");
125 
126  // For tracks w/:
127  // -) A matched shower
128  // -) Shower is in the tracker acceptance (reg != 0)
129  // -) Well defined charge (+/-1),
130  // get the pdf value for each charged particle hypothesis,
131  // and store a ECLPidLikelihood data object.
132  if (mostEnergeticShower && showerReg && std::abs(charge)) {
133 
134  float likelihoods[Const::ChargedStable::c_SetSize];
135  // Initialise PDF value.
136  // This will correspond to a LogL = 0.0 if unchanged.
137  double pdfval(0.0);
138 
139  // Order of loop is defined in UnitConst.cc: e, mu, pi, K, p, d
140  for (const auto& hypo : Const::chargedStableSet) {
141 
142  unsigned int hypo_idx = hypo.getIndex();
143 
144  auto pdgId = hypo.getPDGCode();
145 
146  B2DEBUG(20, "\n\thypo[" << hypo_idx << "] = " << pdgId << ", hypo[" << hypo_idx << "] * reco_charge = " << pdgId * charge);
147 
148  // Retrieve the list of enum ids for the input variables from the payload.
149  auto varids = m_pdfs->getVars(pdgId, charge, p, showerTheta);
150 
151  // Fill the vector w/ the values taken from the module map.
152  std::vector<double> variables;
153  for (const auto& varid : *varids) {
154  variables.push_back(m_variables.at(varid));
155  }
156 
157  // Transform the input variables via gaussianisation+decorrelation only if necessary.
158  if (m_pdfs->doVarsTransfo()) {
159  transfoGaussDecorr(pdgId, charge, p, showerTheta, variables);
160  }
161 
162  // Get the PDF templates for the various observables, and multiply the PDF values for this candidate.
163  // If more than 1 observable is used, this assumes they are independent
164  // (or at least linear correlations have been removed by suitably transforming the inputs...).
165  double prod(1.0);
166  double ipdfval;
167  for (unsigned int idx(0); idx < variables.size(); idx++) {
168 
169  auto var = variables.at(idx);
170  auto varid = varids->at(idx);
171 
172  const TF1* pdf = m_pdfs->getPdf(pdgId, charge, p, showerTheta, varid);
173  if (pdf) {
174 
175  ipdfval = getPdfVal(var, pdf);
176 
177  B2DEBUG(30, "\t\tL(" << pdgId * charge << ") = " << prod
178  << " * pdf(varid: " << static_cast<unsigned int>(varid) << ") = "
179  << prod << " * " << ipdfval
180  << " = " << prod * ipdfval);
181 
182  prod *= ipdfval;
183  }
184  }
185 
186  if (prod != 1.0) {
187  pdfval = prod;
188  }
189 
190  B2DEBUG(20, "\tL(" << pdgId * charge << ") = " << pdfval);
191 
192  likelihoods[hypo_idx] = (std::isnormal(pdfval) && pdfval > 0) ? log(pdfval) : c_dummyLogL;
193 
194  B2DEBUG(20, "\tlog(L(" << pdgId * charge << ")) = " << likelihoods[hypo_idx]);
195 
196  } // Loop over hypotheses
197 
198  const auto eclPidLikelihood = m_eclPidLikelihoods.appendNew(likelihoods);
199 
200  track.addRelationTo(eclPidLikelihood);
201 
202  } // Check on good tracks.
203 
204  } // End loop on tracks
205 }
206 
207 double ECLChargedPIDModule::getPdfVal(const double& x, const TF1* pdf)
208 {
209 
210  double y, xmin, xmax;
211  pdf->GetRange(xmin, xmax);
212 
213  if (x <= xmin) { y = pdf->Eval(xmin + 1e-9); }
214  else if (x >= xmax) { y = pdf->Eval(xmax - 1e-9); }
215  else { y = pdf->Eval(x); }
216 
217  return (y) ? y : 1e-9; // Shall we allow for 0? That translates in LogL = NaN...
218 }
219 
220 void ECLChargedPIDModule::transfoGaussDecorr(const unsigned int pdg, const int charge, const double& p, const double& theta,
221  std::vector<double>& variables)
222 {
223 
224  unsigned int nvars = variables.size();
225 
226  // Get the variable transformation settings for this hypo pdg, p, theta.
227  auto vts = m_pdfs->getVTS(pdg, charge, p, theta);
228 
229  B2DEBUG(30, "");
230  B2DEBUG(30, "\tclass path: " << vts->classPath);
231  B2DEBUG(30, "\tgbin = " << vts->gbin << ", (theta,p) = (" << vts->jth << "," << vts->ip << ")");
232  B2DEBUG(30, "\tnvars: " << nvars);
233 
234  auto varids = m_pdfs->getVars(pdg, charge, p, theta);
235 
236  for (unsigned int ivar(0); ivar < nvars; ivar++) {
237  unsigned int ndivs = vts->nDivisions[ivar];
238  B2DEBUG(30, "\tvarid: " << static_cast<unsigned int>(varids->at(ivar)) << " = " << variables.at(ivar) << ", nsteps = " << ndivs);
239  for (unsigned int jdiv(0); jdiv < ndivs; jdiv++) {
240  auto ij = linIndex(ivar, jdiv, vts->nDivisionsMax);
241  B2DEBUG(30, "\t\tx[" << ivar << "][" << jdiv << "] = x[" << ij << "] = " << vts->x[ij]);
242  B2DEBUG(30, "\t\tcumulDist[" << ivar << "][" << jdiv << "] = cumulDist[" << ij << "] = " << vts->cumulDist[ij]);
243  }
244  }
245 
246  std::vector<double> vtransfo_gauss;
247  vtransfo_gauss.reserve(nvars);
248 
249  for (unsigned int ivar(0); ivar < nvars; ivar++) {
250 
251  int ndivs = vts->nDivisions[ivar];
252 
253  int jdiv = 0;
254  auto ij = linIndex(ivar, jdiv, vts->nDivisionsMax);
255  while (variables.at(ivar) > vts->x[ij]) {
256  jdiv++;
257  ij = linIndex(ivar, jdiv, vts->nDivisionsMax);
258  }
259 
260  if (jdiv < 0) { jdiv = 0; }
261  if (jdiv >= ndivs) { jdiv = ndivs - 1; }
262  int jnextdiv = jdiv;
263  if ((variables.at(ivar) > vts->x[ij] && jdiv != ndivs - 1) || jdiv == 0) {
264  jnextdiv++;
265  } else {
266  jnextdiv--;
267  }
268  auto ijnext = linIndex(ivar, jnextdiv, vts->nDivisionsMax);
269 
270  double dx = vts->x[ij] - vts->x[ijnext];
271  double dy = vts->cumulDist[ij] - vts->cumulDist[ijnext];
272  double cumulant = vts->cumulDist[ij] + (variables.at(ivar) - vts->x[ijnext]) * dy / dx;
273 
274  cumulant = std::min(cumulant, 1.0 - 10e-10);
275  cumulant = std::max(cumulant, 10e-10);
276 
277  double maxErfInvArgRange = 0.99999999;
278  double arg = 2.0 * cumulant - 1.0;
279 
280  arg = std::min(maxErfInvArgRange, arg);
281  arg = std::max(-maxErfInvArgRange, arg);
282 
283  vtransfo_gauss.at(ivar) = c_sqrt2 * TMath::ErfInverse(arg);
284 
285  }
286  B2DEBUG(30, "\tSHOWER properties (Gaussian-transformed):");
287  for (unsigned int idx(0); idx < nvars; idx++) {
288  B2DEBUG(30, "\tvarid: " << static_cast<unsigned int>(varids->at(idx)) << " = " << vtransfo_gauss.at(idx));
289  }
290  B2DEBUG(30, "\t-------------------------------");
291 
292  std::vector<double> vtransfo_decorr;
293  vtransfo_decorr.reserve(nvars);
294 
295  for (unsigned int i(0); i < nvars; i++) {
296  double vartransfo(0);
297  for (unsigned int j(0); j < nvars; j++) {
298  auto ij = linIndex(i, j, nvars);
299  vartransfo += vtransfo_gauss[j] * vts->covMatrix[ij];
300  }
301  vtransfo_decorr.push_back(vartransfo);
302  }
303 
304  B2DEBUG(30, "\tSHOWER properties (Decorrelation-transformed):");
305  for (unsigned int idx(0); idx < nvars; idx++) {
306  B2DEBUG(30, "\tvarid: " << static_cast<unsigned int>(varids->at(idx)) << " = " << vtransfo_decorr.at(idx));
307  }
308  B2DEBUG(30, "\t-------------------------------");
309 
310  // Now modify the input variables vector.
311  for (unsigned int i(0); i < nvars; i++) {
312  variables[i] = vtransfo_decorr.at(i);
313  }
314 
315 }
316 
318 {
319 }
320 
322 {
323 }
static const unsigned int c_SetSize
Number of elements (for use in array bounds etc.)
Definition: Const.h:496
static const ParticleSet chargedStableSet
set of charged stable particles
Definition: Const.h:499
static const ChargedStable pion
charged pion particle
Definition: Const.h:542
The module implements charged particle identification using ECL-related observables.
virtual ~ECLChargedPIDModule()
Destructor, use to clean up anything you created in the constructor.
virtual void initialize() override
Use this to initialize resources or memory your module needs.
DBObjPtr< ECLChargedPidPDFs > m_pdfs
Interface to get the DB payload for ECL charged PID PDFs.
virtual void event() override
Called once for each event.
virtual void endRun() override
Called once when a run ends.
virtual void terminate() override
Clean up anything you created in initialize().
double getPdfVal(const double &x, const TF1 *pdf)
Extract the PDF value for a given variable from the TF1 object.
static constexpr double c_sqrt2
Defintion of sqrt(2)
int linIndex(int i, int j, int m)
Get the index corresponding to element i,j in a linearised n*m array.
StoreObjPtr< EventMetaData > m_eventMetaData
The event information.
static constexpr double c_dummyLogL
Dummy value of Log Likelihood for a particle hypothesis.
void transfoGaussDecorr(const unsigned int pdg, const int charge, const double &p, const double &theta, std::vector< double > &variables)
Transform input variables according to:
void checkPdfsDB()
Check the PDFs payload for consistency everytime they change in the database.
virtual void beginRun() override
Called once before a new run begins.
std::unordered_map< ECLChargedPidPDFs::InputVar, double > m_variables
Map to contain ECL shower observables.
StoreArray< Track > m_tracks
Array of Track objects.
bool m_applyClusterTimingSel
Apply cluster timing selection.
StoreArray< ECLPidLikelihood > m_eclPidLikelihoods
Array of ECLPidLikelihood objects.
@ c_DeltaL
DeltaL (track depth)
@ c_E
Energy of maxE shower.
@ c_LAT
Lateral shower shape.
Class to store ECL Showers.
Definition: ECLShower.h:25
double getPulseShapeDiscriminationMVA() const
Get shower hadron intensity.
Definition: ECLShower.h:394
double getLateralEnergy() const
Get Lateral Energy in Shower.
Definition: ECLShower.h:334
double getE1oE9() const
Get energy ratio E1oE9.
Definition: ECLShower.h:379
double getEnergy() const
Get Energy.
Definition: ECLShower.h:269
double getE9oE21() const
Get energy ratio E9oE21.
Definition: ECLShower.h:384
int getDetectorRegion() const
Return detector region: 0: below acceptance, 1: FWD, 2: BRL, 3: BWD, 11: FWDGAP, 13: BWDGAP.
Definition: ECLShower.h:447
@ c_nPhotons
CR is split into n photons (N1)
Definition: ECLShower.h:37
double getTrkDepth() const
path on track extrapolation to POCA to average cluster direction
Definition: ECLShower.h:344
double getZernikeMVA() const
Get Zernike MVA.
Definition: ECLShower.h:369
double getAbsZernike40() const
Get absolute value of Zernike moment 40.
Definition: ECLShower.h:359
double getTheta() const
Get Theta.
Definition: ECLShower.h:279
double getAbsZernike51() const
Get absolute value of Zernike moment 51.
Definition: ECLShower.h:364
Base class for Modules.
Definition: Module.h:72
Values of the result of a track fit with a given particle hypothesis.
#define REG_MODULE(moduleName)
Register the given module (without 'Module' suffix) with the framework.
Definition: Module.h:650
Abstract base class for different kinds of events.