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