Belle II Software development
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
18using namespace Belle2;
19
20REG_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(); });
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
210double 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
223void 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:615
static const ParticleSet chargedStableSet
set of charged stable particles
Definition: Const.h:618
static const ChargedStable pion
charged pion particle
Definition: Const.h:661
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.
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
#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.