Belle II Software  release-08-01-10
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 /* Own header. */
10 #include <ecl/modules/eclChargedPID/ECLChargedPIDModule.h>
11 
12 /* ROOT headers. */
13 #include <TMath.h>
14 
15 /* C++ headers. */
16 #include <algorithm>
17 
18 using namespace Belle2;
19 
20 REG_MODULE(ECLChargedPID);
21 
22 
24 {
25  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).");
26 
28 
29  addParam("applyClusterTimingSel",
31  "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.",
32  bool(false));
33 }
34 
35 
37 
38 
40 {
41  m_eventMetaData.isRequired();
42 
43  m_eclPidLikelihoods.registerInDataStore();
44  m_tracks.registerRelationTo(m_eclPidLikelihoods);
45 }
46 
47 
49 {
50  if (!m_pdfs) { B2FATAL("No ECL charged PID PDFs payload found in database!"); }
51 }
52 
53 
55 {
56  m_pdfs.addCallback([this]() { checkPdfsDB(); });
57  checkPdfsDB();
58 }
59 
60 
62 {
63 
64  for (const auto& track : m_tracks) {
65 
66  // Don't forget to clear variable map before a track gets processed!
67  m_variables.clear();
68 
69  // Load the pion fit hypothesis or the hypothesis which is the closest in mass to a pion
70  // (the tracking will not always successfully fit with a pion hypothesis).
71  const TrackFitResult* fitRes = track.getTrackFitResultWithClosestMass(Const::pion);
72  if (fitRes == nullptr) continue;
73  const auto relShowers = track.getRelationsTo<ECLShower>();
74  if (relShowers.size() == 0) continue;
75 
76  const double p = fitRes->getMomentum().R();
77  const double theta = fitRes->getMomentum().Theta();
78  const auto charge = fitRes->getChargeSign();
79 
80  double shEnergy(0.0), maxEnergy(0.0);
81 
82  const ECLShower* mostEnergeticShower = nullptr;
83 
84  for (const auto& eclShower : relShowers) {
85 
86  if (eclShower.getHypothesisId() != ECLShower::c_nPhotons) continue;
88  if (abs(eclShower.getTime()) > eclShower.getDeltaTime99()) continue;
89  }
90 
91  shEnergy = eclShower.getEnergy();
92  if (shEnergy > maxEnergy) {
93  maxEnergy = shEnergy;
94  mostEnergeticShower = &eclShower;
95  }
96 
97  }
98 
99  double showerTheta = (mostEnergeticShower) ? mostEnergeticShower->getTheta() : -999.0;
100  int showerReg = (mostEnergeticShower) ? mostEnergeticShower->getDetectorRegion() : -1;
101 
102  // These are the variables that can be used to extract PDF templates for the likelihood / for the MVA training.
103  m_variables[ECLChargedPidPDFs::InputVar::c_E1E9] = (mostEnergeticShower) ? mostEnergeticShower->getE1oE9() : -1.0;
104  m_variables[ECLChargedPidPDFs::InputVar::c_E9E21] = (mostEnergeticShower) ? mostEnergeticShower->getE9oE21() : -1.0;
105  m_variables[ECLChargedPidPDFs::InputVar::c_E] = (mostEnergeticShower) ? maxEnergy : -1.0;
106  m_variables[ECLChargedPidPDFs::InputVar::c_EoP] = (mostEnergeticShower) ? maxEnergy / p : -1.0;
107  m_variables[ECLChargedPidPDFs::InputVar::c_Z40] = (mostEnergeticShower) ? mostEnergeticShower->getAbsZernikeMoment(4, 0) : -999.0;
108  m_variables[ECLChargedPidPDFs::InputVar::c_Z51] = (mostEnergeticShower) ? mostEnergeticShower->getAbsZernikeMoment(5, 1) : -999.0;
109  m_variables[ECLChargedPidPDFs::InputVar::c_ZMVA] = (mostEnergeticShower) ? mostEnergeticShower->getZernikeMVA() : -999.0;
110  m_variables[ECLChargedPidPDFs::InputVar::c_PSDMVA] = (mostEnergeticShower) ? mostEnergeticShower->getPulseShapeDiscriminationMVA() :
111  -999.0;
112  m_variables[ECLChargedPidPDFs::InputVar::c_DeltaL] = (mostEnergeticShower) ? mostEnergeticShower->getTrkDepth() : -1.0;
113  m_variables[ECLChargedPidPDFs::InputVar::c_LAT] = (mostEnergeticShower) ? mostEnergeticShower->getLateralEnergy() : -999.0;
114 
115  B2DEBUG(20, "EVENT: " << m_eventMetaData->getEvent());
116  B2DEBUG(20, "-------------------------------");
117  B2DEBUG(20, "TRACK properties:");
118  B2DEBUG(20, "p = " << p << " [GeV/c]");
119  B2DEBUG(30, "theta = " << theta << " [rad]");
120  B2DEBUG(20, "charge = " << charge);
121  B2DEBUG(20, "-------------------------------");
122  B2DEBUG(20, "SHOWER properties:");
123  B2DEBUG(20, "showerTheta = " << showerTheta << " [rad], showerReg = " << showerReg);
124  for (const auto& [varid, var] : m_variables) {
125  B2DEBUG(30, "varid: " << static_cast<unsigned int>(varid) << " = " << var);
126  }
127  B2DEBUG(20, "-------------------------------");
128 
129  // For tracks w/:
130  // -) A matched shower
131  // -) Shower is in the tracker acceptance (reg != 0)
132  // -) Well defined charge (+/-1),
133  // get the pdf value for each charged particle hypothesis,
134  // and store a ECLPidLikelihood data object.
135  if (mostEnergeticShower && showerReg && std::abs(charge)) {
136 
137  float likelihoods[Const::ChargedStable::c_SetSize];
138  // Initialise PDF value.
139  // This will correspond to a LogL = 0.0 if unchanged.
140  double pdfval(0.0);
141 
142  // Order of loop is defined in UnitConst.cc: e, mu, pi, K, p, d
143  for (const auto& hypo : Const::chargedStableSet) {
144 
145  unsigned int hypo_idx = hypo.getIndex();
146 
147  auto pdgId = hypo.getPDGCode();
148 
149  B2DEBUG(20, "\n\thypo[" << hypo_idx << "] = " << pdgId << ", hypo[" << hypo_idx << "] * reco_charge = " << pdgId * charge);
150 
151  // Retrieve the list of enum ids for the input variables from the payload.
152  auto varids = m_pdfs->getVars(pdgId, charge, p, showerTheta);
153 
154  // Fill the vector w/ the values taken from the module map.
155  std::vector<double> variables;
156  for (const auto& varid : *varids) {
157  variables.push_back(m_variables.at(varid));
158  }
159 
160  // Transform the input variables via gaussianisation+decorrelation only if necessary.
161  if (m_pdfs->doVarsTransfo()) {
162  transfoGaussDecorr(pdgId, charge, p, showerTheta, variables);
163  }
164 
165  // Get the PDF templates for the various observables, and multiply the PDF values for this candidate.
166  // If more than 1 observable is used, this assumes they are independent
167  // (or at least linear correlations have been removed by suitably transforming the inputs...).
168  double prod(1.0);
169  double ipdfval;
170  for (unsigned int idx(0); idx < variables.size(); idx++) {
171 
172  auto var = variables.at(idx);
173  auto varid = varids->at(idx);
174 
175  const TF1* pdf = m_pdfs->getPdf(pdgId, charge, p, showerTheta, varid);
176  if (pdf) {
177 
178  ipdfval = getPdfVal(var, pdf);
179 
180  B2DEBUG(30, "\t\tL(" << pdgId * charge << ") = " << prod
181  << " * pdf(varid: " << static_cast<unsigned int>(varid) << ") = "
182  << prod << " * " << ipdfval
183  << " = " << prod * ipdfval);
184 
185  prod *= ipdfval;
186  }
187  }
188 
189  if (prod != 1.0) {
190  pdfval = prod;
191  }
192 
193  B2DEBUG(20, "\tL(" << pdgId * charge << ") = " << pdfval);
194 
195  likelihoods[hypo_idx] = (std::isnormal(pdfval) && pdfval > 0) ? log(pdfval) : c_dummyLogL;
196 
197  B2DEBUG(20, "\tlog(L(" << pdgId * charge << ")) = " << likelihoods[hypo_idx]);
198 
199  } // Loop over hypotheses
200 
201  const auto eclPidLikelihood = m_eclPidLikelihoods.appendNew(likelihoods);
202 
203  track.addRelationTo(eclPidLikelihood);
204 
205  } // Check on good tracks.
206 
207  } // End loop on tracks
208 }
209 
210 double ECLChargedPIDModule::getPdfVal(const double& x, const TF1* pdf)
211 {
212 
213  double y, xmin, xmax;
214  pdf->GetRange(xmin, xmax);
215 
216  if (x <= xmin) { y = pdf->Eval(xmin + 1e-9); }
217  else if (x >= xmax) { y = pdf->Eval(xmax - 1e-9); }
218  else { y = pdf->Eval(x); }
219 
220  return (y) ? y : 1e-9; // Shall we allow for 0? That translates in LogL = NaN...
221 }
222 
223 void ECLChargedPIDModule::transfoGaussDecorr(const unsigned int pdg, const int charge, const double& p, const double& theta,
224  std::vector<double>& variables)
225 {
226 
227  unsigned int nvars = variables.size();
228 
229  // Get the variable transformation settings for this hypo pdg, p, theta.
230  auto vts = m_pdfs->getVTS(pdg, charge, p, theta);
231 
232  B2DEBUG(30, "");
233  B2DEBUG(30, "\tclass path: " << vts->classPath);
234  B2DEBUG(30, "\tgbin = " << vts->gbin << ", (theta,p) = (" << vts->jth << "," << vts->ip << ")");
235  B2DEBUG(30, "\tnvars: " << nvars);
236 
237  auto varids = m_pdfs->getVars(pdg, charge, p, theta);
238 
239  for (unsigned int ivar(0); ivar < nvars; ivar++) {
240  unsigned int ndivs = vts->nDivisions[ivar];
241  B2DEBUG(30, "\tvarid: " << static_cast<unsigned int>(varids->at(ivar)) << " = " << variables.at(ivar) << ", nsteps = " << ndivs);
242  for (unsigned int jdiv(0); jdiv < ndivs; jdiv++) {
243  auto ij = linIndex(ivar, jdiv, vts->nDivisionsMax);
244  B2DEBUG(30, "\t\tx[" << ivar << "][" << jdiv << "] = x[" << ij << "] = " << vts->x[ij]);
245  B2DEBUG(30, "\t\tcumulDist[" << ivar << "][" << jdiv << "] = cumulDist[" << ij << "] = " << vts->cumulDist[ij]);
246  }
247  }
248 
249  std::vector<double> vtransfo_gauss;
250  vtransfo_gauss.reserve(nvars);
251 
252  for (unsigned int ivar(0); ivar < nvars; ivar++) {
253 
254  int ndivs = vts->nDivisions[ivar];
255 
256  int jdiv = 0;
257  auto ij = linIndex(ivar, jdiv, vts->nDivisionsMax);
258  while (variables.at(ivar) > vts->x[ij]) {
259  jdiv++;
260  ij = linIndex(ivar, jdiv, vts->nDivisionsMax);
261  }
262 
263  if (jdiv < 0) { jdiv = 0; }
264  if (jdiv >= ndivs) { jdiv = ndivs - 1; }
265  int jnextdiv = jdiv;
266  if ((variables.at(ivar) > vts->x[ij] && jdiv != ndivs - 1) || jdiv == 0) {
267  jnextdiv++;
268  } else {
269  jnextdiv--;
270  }
271  auto ijnext = linIndex(ivar, jnextdiv, vts->nDivisionsMax);
272 
273  double dx = vts->x[ij] - vts->x[ijnext];
274  double dy = vts->cumulDist[ij] - vts->cumulDist[ijnext];
275  double cumulant = vts->cumulDist[ij] + (variables.at(ivar) - vts->x[ijnext]) * dy / dx;
276 
277  cumulant = std::min(cumulant, 1.0 - 10e-10);
278  cumulant = std::max(cumulant, 10e-10);
279 
280  double maxErfInvArgRange = 0.99999999;
281  double arg = 2.0 * cumulant - 1.0;
282 
283  arg = std::min(maxErfInvArgRange, arg);
284  arg = std::max(-maxErfInvArgRange, arg);
285 
286  vtransfo_gauss.at(ivar) = c_sqrt2 * TMath::ErfInverse(arg);
287 
288  }
289  B2DEBUG(30, "\tSHOWER properties (Gaussian-transformed):");
290  for (unsigned int idx(0); idx < nvars; idx++) {
291  B2DEBUG(30, "\tvarid: " << static_cast<unsigned int>(varids->at(idx)) << " = " << vtransfo_gauss.at(idx));
292  }
293  B2DEBUG(30, "\t-------------------------------");
294 
295  std::vector<double> vtransfo_decorr;
296  vtransfo_decorr.reserve(nvars);
297 
298  for (unsigned int i(0); i < nvars; i++) {
299  double vartransfo(0);
300  for (unsigned int j(0); j < nvars; j++) {
301  auto ij = linIndex(i, j, nvars);
302  vartransfo += vtransfo_gauss[j] * vts->covMatrix[ij];
303  }
304  vtransfo_decorr.push_back(vartransfo);
305  }
306 
307  B2DEBUG(30, "\tSHOWER properties (Decorrelation-transformed):");
308  for (unsigned int idx(0); idx < nvars; idx++) {
309  B2DEBUG(30, "\tvarid: " << static_cast<unsigned int>(varids->at(idx)) << " = " << vtransfo_decorr.at(idx));
310  }
311  B2DEBUG(30, "\t-------------------------------");
312 
313  // Now modify the input variables vector.
314  for (unsigned int i(0); i < nvars; i++) {
315  variables[i] = vtransfo_decorr.at(i);
316  }
317 
318 }
319 
321 {
322 }
323 
325 {
326 }
static const unsigned int c_SetSize
Number of elements (for use in array bounds etc.)
Definition: Const.h:606
static const ParticleSet chargedStableSet
set of charged stable particles
Definition: Const.h:609
static const ChargedStable pion
charged pion particle
Definition: Const.h:652
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)
ECLChargedPIDModule()
Constructor, for setting module description and parameters.
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:30
double getAbsZernikeMoment(unsigned int n, unsigned int m) const
Get absolute value of Zernike Moment nm.
Definition: ECLShower.h:377
double getPulseShapeDiscriminationMVA() const
Get shower hadron intensity.
Definition: ECLShower.h:407
double getLateralEnergy() const
Get Lateral Energy in Shower.
Definition: ECLShower.h:352
double getE1oE9() const
Get energy ratio E1oE9.
Definition: ECLShower.h:392
double getEnergy() const
Get Energy.
Definition: ECLShower.h:287
double getE9oE21() const
Get energy ratio E9oE21.
Definition: ECLShower.h:397
int getDetectorRegion() const
Return detector region: 0: below acceptance, 1: FWD, 2: BRL, 3: BWD, 11: FWDGAP, 13: BWDGAP.
Definition: ECLShower.h:480
@ c_nPhotons
CR is split into n photons (N1)
Definition: ECLShower.h:42
double getTrkDepth() const
path on track extrapolation to POCA to average cluster direction
Definition: ECLShower.h:362
double getZernikeMVA() const
Get Zernike MVA.
Definition: ECLShower.h:382
double getTheta() const
Get Theta.
Definition: ECLShower.h:297
Base class for Modules.
Definition: Module.h:72
void setDescription(const std::string &description)
Sets the description of the module.
Definition: Module.cc:214
void setPropertyFlags(unsigned int propertyFlags)
Sets the flags for the module properties.
Definition: Module.cc:208
@ c_ParallelProcessingCertified
This module can be run in parallel processing mode safely (All I/O must be done through the data stor...
Definition: Module.h:80
Values of the result of a track fit with a given particle hypothesis.
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
Abstract base class for different kinds of events.