ATLAS Offline Software
Loading...
Searching...
No Matches
AsgPhotonBDTSelector.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
6
8
9#include "TEnv.h"
10
13
14#include <algorithm>
15#include <cmath>
16#include <sstream>
17
18namespace {
19
20 enum PhotonBDTIsEMBits : unsigned int {
21 // Eta Out of Range
22 FailOutOfRange = 1u << 0, // 1
23
24 // Fail preselections
25 FailPreselectionF1 = 1u << 1, // 2
26 FailPreselectionE277 = 1u << 2, // 4
27
28 // Fail BDT score
29 FailBDTScore = 1u << 3, // 8
30
31 // Cannot compute score or the score is missing
32 FailMissingScore = 1u << 4 // 16
33 };
34
35} // end of anonymous namespace
36
37namespace PhotonIDBDT {
38
39//=============================================================================
40// Standard constructor
41//=============================================================================
43 : asg::AsgTool(name),
44 m_configFile("")
45{}
46
47//=============================================================================
48// Initialise the tool: load config and retrieve BDT calculator
49//=============================================================================
51 // Load the configuration file and parse it
53 // Register the cuts in the AcceptInfo
54 m_cutPosHasScore = m_acceptInfo.addCut("HasScore", "Photon has BDT score decoration");
55 m_cutPosPreF1 = m_acceptInfo.addCut("PreselectionF1", "Photon passes preselection on f1");
56 m_cutPosPreE277 = m_acceptInfo.addCut("PreselectionE277", "Photon passes preselection on e277");
57 m_cutPosPassPreselection = m_acceptInfo.addCut("PassPreselection", "Photon passes all preselections");
58 m_cutPosInRange = m_acceptInfo.addCut("InRange", "Photon kinematics within eta range and binned in Et");
59 m_cutPosScore = m_acceptInfo.addCut("BDTScore", "Passes the BDT score cut");
60 // Check if it went well
61 if (m_cutPosScore < 0 || m_cutPosInRange < 0 || m_cutPosHasScore < 0 ||
63 ATH_MSG_ERROR("Failed to register cuts in AcceptInfo");
64 return StatusCode::FAILURE;
65 }
66 // Retrieve PhotonBDTCalculator tool
67 ATH_CHECK(m_bdtTool.retrieve());
68
69 return StatusCode::SUCCESS;
70}
71
72//=============================================================================
73// Load and parse the configuration file
74//=============================================================================
76 // If we specified the WP, look for the corresponding config file in the mapping
77 if (!m_workingPoint.empty()) {
80 );
81 ATH_MSG_INFO("Photon ID BDT working point: " << getOperatingPointName());
82 }
83
84 if (m_configFile.empty()) {
85 ATH_MSG_ERROR("Empty configFile. WorkingPoint: " << m_workingPoint);
86 return StatusCode::FAILURE;
87 }
88
90 if (configFile.empty()) {
91 ATH_MSG_ERROR("Could not locate config via PathResolver: " << m_configFile);
92 return StatusCode::FAILURE;
93 }
94
95 ATH_MSG_DEBUG("Using config file: " << m_configFile << " (resolved: " << configFile << ")");
96
97 // Parse config file
98 TEnv env;
99 env.ReadFile(configFile.c_str(), kEnvLocal);
100
101 // Load WP binning
102 m_etaBins = AsgConfigHelper::HelperFloat("CutBinEta", env);
103 m_etBinsGeV = AsgConfigHelper::HelperFloat("CutBinEtGeV", env);
104 // Load preselection cuts on f1 and e277 variables
105 m_cutF1Conv = AsgConfigHelper::HelperFloat("CutF1Conv", env);
106 m_cutF1Unconv = AsgConfigHelper::HelperFloat("CutF1Unconv", env);
107 m_cutE277Conv = AsgConfigHelper::HelperFloat("CutE277Conv", env);
108 m_cutE277Unconv= AsgConfigHelper::HelperFloat("CutE277Unconv", env);
109 // Load BDT score cuts
110 m_cutConv = AsgConfigHelper::HelperFloat("BDTCutConv", env);
111 m_cutUnconv = AsgConfigHelper::HelperFloat("BDTCutUnconv", env);
112
113 // Validate binning
114 const unsigned nEta = (m_etaBins.size() >= 2) ? (m_etaBins.size() - 1) : 0;
115 const unsigned nEt = (m_etBinsGeV.size() >= 2) ? (m_etBinsGeV.size() - 1) : 0;
116
117 if (nEta == 0 || nEt == 0) {
118 ATH_MSG_ERROR("Need at least 2 edges for eta and Et binning.");
119 return StatusCode::FAILURE;
120 }
121
122 const unsigned nExpected = nEta * nEt;
123 if (m_cutConv.size() != nExpected || m_cutUnconv.size() != nExpected) {
124 ATH_MSG_ERROR("Size mismatch between eta and Et binning and BDT cut maps: expected " << nExpected
125 << " (= " << nEta << "*" << nEt << ")"
126 << " got BDTCutConv=" << m_cutConv.size()
127 << " BDTCutUnconv=" << m_cutUnconv.size());
128 return StatusCode::FAILURE;
129 }
130
131 return StatusCode::SUCCESS;
132}
133
134//=============================================================================
135// Return the name of the operating point
136//=============================================================================
138{
139 return m_workingPoint;
140}
141
142//=============================================================================
143// Return accept info object describing the cuts
144//=============================================================================
148
149//=============================================================================
150// Accept and execute methods
151//=============================================================================
152
154{
155 return accept(Gaudi::Hive::currentContext(), part);
156}
157
159 const xAOD::IParticle* part) const
160{
161 if (!part) return makeReject(m_acceptInfo);
162
163 if (const auto* ph = dynamic_cast<const xAOD::Photon*>(part)) {
164 return accept(ctx, ph);
165 }
166 if (const auto* eg = dynamic_cast<const xAOD::Egamma*>(part)) {
167 return accept(ctx, eg);
168 }
169 return makeReject(m_acceptInfo);
170}
171
173 const xAOD::Egamma* eg) const
174{
175 if (!eg) return makeReject(m_acceptInfo);
176
177 const auto* ph = dynamic_cast<const xAOD::Photon*>(eg);
178 if (!ph) return makeReject(m_acceptInfo);
179
180 return accept(ctx, ph);
181}
182
184 const xAOD::Photon* ph) const
185{
186 if (!ph) return makeReject(m_acceptInfo);
187 return acceptBDT(ctx, *ph, nullptr);
188}
189
191 const xAOD::Electron*) const
192{
193 // This tool is photon-only
194 return makeReject(m_acceptInfo);
195}
196
197
198StatusCode AsgPhotonBDTSelector::execute(const EventContext& ctx,
199 const xAOD::Egamma* eg,
200 unsigned int& isEM) const
201{
202 isEM = 0u;
203 if (!eg) return StatusCode::SUCCESS;
204
205 const auto* ph = dynamic_cast<const xAOD::Photon*>(eg);
206 if (!ph) {
207 isEM = 1u; // or define a bit for wrong type
208 return StatusCode::SUCCESS;
209 }
210
211 (void) acceptBDT(ctx, *ph, &isEM);
212 return StatusCode::SUCCESS;
213}
214
215//=============================================================================
216// Helpers for cut applications
217//=============================================================================
221
222
223bool AsgPhotonBDTSelector::findBin(const float absEta, const float etGeV,
224 size_t& iEta, size_t& iEt) const {
225 // bins defined as [edge_i, edge_{i+1})
226 // Eta binning
227 auto itEta = std::upper_bound(m_etaBins.begin(), m_etaBins.end(), absEta);
228 if (itEta == m_etaBins.begin() || itEta == m_etaBins.end()) return false; // Eta out of range
229 iEta = (itEta - m_etaBins.begin()) - 1; // regular bin
230
231 // ET binning
232 auto itEt = std::upper_bound(m_etBinsGeV.begin(), m_etBinsGeV.end(), etGeV);
233 if (itEt == m_etBinsGeV.begin()) { iEt = 0; } // underflow: first bin
234 else if (itEt == m_etBinsGeV.end()) { iEt = m_etBinsGeV.size() - 2; } // overflow: last bin
235 else { iEt = (itEt - m_etBinsGeV.begin()) - 1; } // regular bin
236
237 return true;
238}
239
240float AsgPhotonBDTSelector::getCut(const bool converted, const size_t iEta, const size_t iEt) const {
241 const size_t nEta = m_etaBins.size() - 1;
242 const size_t idx = iEt * nEta + iEta;
243 const float cut = converted ? m_cutConv.at(idx) : m_cutUnconv.at(idx);
244 return cut;
245}
246
248 asg::AcceptData acc(&info);
249 for (unsigned i = 0; i < info.getNCuts(); ++i) acc.setCutResult(i, false);
250 return acc;
251}
252
254 float out = 0.f;
255 if (!ph.showerShapeValue(out, t)) {
256 ATH_MSG_ERROR("AsgPhotonBDTSelector: missing shower shape variable '" << name);
257 // Fail loudly
258 throw std::runtime_error(std::string("AsgPhotonBDTSelector: missing shower shape ") + name);
259 }
260 return out;
261}
262
263//=============================================================================
264// Accept method: apply cuts and return accept data
265//=============================================================================
266asg::AcceptData AsgPhotonBDTSelector::acceptBDT(const EventContext& /*ctx*/, const xAOD::Photon& ph, unsigned int* isEM) const {
267 // Helper for isEM word
268 auto setBit = [&](unsigned int bit) {
269 if (isEM) *isEM |= bit;
270 };
271 // I assume that the photon exists and is valid
272
273 // start to retrieve the acceptor
274 // Start with all cuts failed
276
277 // Ensure BDT score is available
278 const SG::AuxElement::Accessor<float> accScore(m_scoreDecoration);
279 bool hasScore = accScore.isAvailable(ph);
280 // If the score is not available, we try to compute it on the fly
281 if (!hasScore && m_computeIfMissing) {
282 if (m_bdtTool->decorate(ph).isSuccess()) {
283 hasScore = accScore.isAvailable(ph);
284 }
285 }
286 // if we are not allowed to compute the score and it's not there
287 // reject and set the bit for missing score
288 else if (!hasScore) {
289 setBit(FailMissingScore);
290 return acc;
291 }
292 // now we should have the score available, if we recomputed it
293 // If it is not available even after trying to compute, reject and set the bit for missing score
294 if (!hasScore) {
295 setBit(FailMissingScore);
296 return acc;
297 }
298
299 // Ok now we assume that we have the score
300 const float score = accScore(ph);
301 acc.setCutResult(m_cutPosHasScore, true);
302
303 // Now we check the photon kinematics and the binning
304 const float absEta = std::abs(ph.eta());
305 const float etGeV = ph.pt() * 1e-3f;
306
307 size_t iEta=0, iEt=0;
308 if (!findBin(absEta, etGeV, iEta, iEt)) {
309 setBit(FailOutOfRange); // failOutOfRange
310 return acc;
311 }
312 // If we are here, the photon is in the correct eta range
313 acc.setCutResult(m_cutPosInRange, true);
314
315 // check if the photon is converted
316 const bool conv = isConverted(ph);
317
318 // Check F1 and e277 preselection cuts
319 bool passF1 = false, passE277 = false, passPre = false;
320 // Before trying to access the shower shape variables, we check if they are available.
321 // If not, we can either fail or reapply the WP based on the score and isEM word (if enabled and available)
323 float tmp = 0.f;
324 const bool hasF1 = ph.showerShapeValue(tmp, xAOD::EgammaParameters::f1);
325 const bool hasE277 = ph.showerShapeValue(tmp, xAOD::EgammaParameters::e277);
326 if (!hasF1 || !hasE277) {
327 // Check if isEM decoration is available
328 const SG::AuxElement::Accessor<int> accIsEM(m_isEMDecoration);
329 if (accIsEM.isAvailable(ph)) {
330 const int previousIsEM = accIsEM(ph);
331 passF1 = !(previousIsEM & FailPreselectionF1);
332 passE277 = !(previousIsEM & FailPreselectionE277);
333 passPre = passF1 && passE277;
334 }
335 else {
336 ATH_MSG_ERROR("Missing f1 and e277 shower shapes and isEM decoration, cannot reapply WP. Rejecting photon.");
337 acc.setCutResult(m_cutPosPreF1, false);
338 acc.setCutResult(m_cutPosPreE277, false);
339 acc.setCutResult(m_cutPosPassPreselection, false);
340 setBit(FailPreselectionF1);
341 setBit(FailPreselectionE277);
342 return acc;
343 }
344 }
345 }
346 else {
347 // If we are missing shower shapes and we are not reapplying the WP, we throw an error
348 float f1 = 0.f, e277 = 0.f;
351
352 const float cutF1 = conv ? m_cutF1Conv.at(0) : m_cutF1Unconv.at(0);
353 const float cutE277 = conv ? m_cutE277Conv.at(0) : m_cutE277Unconv.at(0);
354 passF1 = (f1 > cutF1);
355 passE277 = (e277 > cutE277);
356 passPre = passF1 && passE277;
357 }
358
359 // Decorate the accept data with the results of the preselection cuts
360 acc.setCutResult(m_cutPosPreF1, passF1);
361 acc.setCutResult(m_cutPosPreE277, passE277);
362 acc.setCutResult(m_cutPosPassPreselection, passPre);
363
364 // Set bits for failed preselections
365 if (!passF1) setBit(FailPreselectionF1);
366 if (!passE277) setBit(FailPreselectionE277);
367 // If failed preselection, reject and return
368 if (!passPre) return acc;
369
370 // Check the BDT score cut
371 const float cut = getCut(conv, iEta, iEt);
372 const bool passBDT = (score > cut);
373 acc.setCutResult(m_cutPosScore, (score > cut));
374 if (!passBDT) setBit(FailBDTScore);
375
376 return acc;
377}
378
379} // namespace PhotonIDBDT
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_DEBUG(x)
std::string PathResolverFindCalibFile(const std::string &logical_file_name)
static void setBit(unsigned char &field, unsigned num, bool val)
Gaudi::Property< std::string > m_workingPoint
float getShowerShape(const xAOD::Photon &ph, xAOD::EgammaParameters::ShowerShapeType t, const char *name="") const
virtual std::string getOperatingPointName() const override
Report the current operating point.
Gaudi::Property< bool > m_reapplyWPIfNoShowerShapes
bool isConverted(const xAOD::Photon &ph) const
AsgPhotonBDTSelector(const std::string &name)
virtual const asg::AcceptInfo & getAcceptInfo() const override
Declare the interface ID for this pure-virtual interface class to the Athena framework.
ToolHandle< PhotonBDTCalculator > m_bdtTool
asg::AcceptData acceptBDT(const EventContext &ctx, const xAOD::Photon &ph, unsigned int *isEM=nullptr) const
virtual StatusCode initialize() override
Dummy implementation of the initialisation function.
Gaudi::Property< std::string > m_scoreDecoration
asg::AcceptData makeReject(const asg::AcceptInfo &info) const
float getCut(const bool converted, const size_t iEta, const size_t iEt) const
virtual asg::AcceptData accept(const xAOD::IParticle *part) const override
accept with pointer to IParticle so as to not hide the IAsgSelectionTool one
Gaudi::Property< bool > m_computeIfMissing
virtual StatusCode execute(const EventContext &ctx, const xAOD::Egamma *eg, unsigned int &isEM) const override
Add a legacy execute method - return isEM value.
Gaudi::Property< std::string > m_isEMDecoration
bool findBin(const float absEta, const float etGeV, size_t &iEta, size_t &iEt) const
AsgTool(const std::string &name)
Constructor specifying the tool instance's name.
Definition AsgTool.cxx:58
virtual double pt() const override final
The transverse momentum ( ) of the particle.
Definition Egamma_v1.cxx:66
bool showerShapeValue(float &value, const EgammaParameters::ShowerShapeType information) const
Accessor for ShowerShape values.
virtual double eta() const override final
The pseudorapidity ( ) of the particle.
Definition Egamma_v1.cxx:71
Class providing the definition of the 4-vector interface.
std::string findConfigFile(const std::string &input, const std::map< std::string, std::string > &configmap)
std::vector< float > HelperFloat(const std::string &input, TEnv &env)
const std::map< std::string, std::string > PhotonBDTPointToConfFile
bool isConvertedPhoton(const xAOD::Egamma *eg, bool excludeTRT=false)
is the object a converted photon
@ e277
uncalibrated energy (sum of cells) of the middle sampling in a rectangle of size 7x7
Definition EgammaEnums.h:81
@ f1
E1/E = fraction of energy reconstructed in the first sampling, where E1 is energy in all strips belon...
Definition EgammaEnums.h:53
Egamma_v1 Egamma
Definition of the current "egamma version".
Definition Egamma.h:17
Photon_v1 Photon
Definition of the current "egamma version".
Electron_v1 Electron
Definition of the current "egamma version".