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