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
9
10#include "TEnv.h"
11
15
16#include <algorithm>
17#include <cmath>
18#include <sstream>
19
20namespace {
21
22 enum PhotonBDTIsEMBits : unsigned int {
23 // Eta Out of Range
24 FailOutOfRange = 1u << 0, // 1
25
26 // Fail preselections
27 FailPreselectionF1 = 1u << 1, // 2
28 FailPreselectionE277 = 1u << 2, // 4
29
30 // Fail BDT score
31 FailBDTScore = 1u << 3, // 8
32
33 // Cannot compute score or the score is missing
34 FailMissingScore = 1u << 4 // 16
35 };
36
37} // end of anonymous namespace
38
39namespace PhotonIDBDT {
40
41//=============================================================================
42// Initialise the tool: load config and retrieve BDT calculator
43//=============================================================================
45 // Load the configuration file and parse it
47 // Register the cuts in the AcceptInfo
48 m_cutPosHasScore = m_acceptInfo.addCut("HasScore", "Photon has BDT score decoration");
49 m_cutPosPreF1 = m_acceptInfo.addCut("PreselectionF1", "Photon passes preselection on f1");
50 m_cutPosPreE277 = m_acceptInfo.addCut("PreselectionE277", "Photon passes preselection on e277");
51 m_cutPosPassPreselection = m_acceptInfo.addCut("PassPreselection", "Photon passes all preselections");
52 m_cutPosInRange = m_acceptInfo.addCut("InRange", "Photon kinematics within eta range and binned in Et");
53 m_cutPosScore = m_acceptInfo.addCut("BDTScore", "Passes the BDT score cut");
54 // Check if it went well
55 if (m_cutPosScore < 0 || m_cutPosInRange < 0 || m_cutPosHasScore < 0 ||
57 ATH_MSG_ERROR("Failed to register cuts in AcceptInfo");
58 return StatusCode::FAILURE;
59 }
60
61 ATH_CHECK(m_ContainerName.initialize());
62 ATH_CHECK(m_decoratorScore.initialize());
63
64#ifndef XAOD_STANDALONE
66 // The user has promised that this will be produced by the same alg.
67 // Tell the scheduler to ignore it to avoid circular dependencies.
69 }
70#endif
71
72 return StatusCode::SUCCESS;
73}
74
75//=============================================================================
76// Load and parse the configuration file
77//=============================================================================
79 // If we specified the WP, look for the corresponding config file in the mapping
80 if (!m_workingPoint.empty()) {
83 );
84 ATH_MSG_INFO("Photon ID BDT working point: " << getOperatingPointName());
85 }
86
87 if (m_configFile.empty()) {
88 ATH_MSG_ERROR("Empty configFile. WorkingPoint: " << m_workingPoint);
89 return StatusCode::FAILURE;
90 }
91
93 if (configFile.empty()) {
94 ATH_MSG_ERROR("Could not locate config via PathResolver: " << m_configFile);
95 return StatusCode::FAILURE;
96 }
97
98 ATH_MSG_DEBUG("Using config file: " << m_configFile << " (resolved: " << configFile << ")");
99
100 // Parse config file
101 TEnv env;
102 env.ReadFile(configFile.c_str(), kEnvLocal);
103
104 // Load WP binning
105 m_etaBins = AsgConfigHelper::HelperFloat("CutBinEta", env);
106 m_etBinsGeV = AsgConfigHelper::HelperFloat("CutBinEtGeV", env);
107 // Load preselection cuts on f1 and e277 variables
108 m_cutF1Conv = AsgConfigHelper::HelperFloat("CutF1Conv", env);
109 m_cutF1Unconv = AsgConfigHelper::HelperFloat("CutF1Unconv", env);
110 m_cutE277Conv = AsgConfigHelper::HelperFloat("CutE277Conv", env);
111 m_cutE277Unconv= AsgConfigHelper::HelperFloat("CutE277Unconv", env);
112 // Load BDT score cuts
113 m_cutConv = AsgConfigHelper::HelperFloat("BDTCutConv", env);
114 m_cutUnconv = AsgConfigHelper::HelperFloat("BDTCutUnconv", env);
115
116 // Validate binning
117 const unsigned nEta = (m_etaBins.size() >= 2) ? (m_etaBins.size() - 1) : 0;
118 const unsigned nEt = (m_etBinsGeV.size() >= 2) ? (m_etBinsGeV.size() - 1) : 0;
119
120 if (nEta == 0 || nEt == 0) {
121 ATH_MSG_ERROR("Need at least 2 edges for eta and Et binning.");
122 return StatusCode::FAILURE;
123 }
124
125 const unsigned nExpected = nEta * nEt;
126 if (m_cutConv.size() != nExpected || m_cutUnconv.size() != nExpected) {
127 ATH_MSG_ERROR("Size mismatch between eta and Et binning and BDT cut maps: expected " << nExpected
128 << " (= " << nEta << "*" << nEt << ")"
129 << " got BDTCutConv=" << m_cutConv.size()
130 << " BDTCutUnconv=" << m_cutUnconv.size());
131 return StatusCode::FAILURE;
132 }
133
134 return StatusCode::SUCCESS;
135}
136
137//=============================================================================
138// Return the name of the operating point
139//=============================================================================
141{
142 return m_workingPoint;
143}
144
145//=============================================================================
146// Return accept info object describing the cuts
147//=============================================================================
151
152//=============================================================================
153// Accept and execute methods
154//=============================================================================
155
157{
158 return accept(Gaudi::Hive::currentContext(), part);
159}
160
162 const xAOD::IParticle* part) const
163{
164 if (!part) return makeReject(m_acceptInfo);
165
166 if (const auto* ph = dynamic_cast<const xAOD::Photon*>(part)) {
167 return accept(ctx, ph);
168 }
169 if (const auto* eg = dynamic_cast<const xAOD::Egamma*>(part)) {
170 return accept(ctx, eg);
171 }
172 return makeReject(m_acceptInfo);
173}
174
176 const xAOD::Egamma* eg) const
177{
178 if (!eg) return makeReject(m_acceptInfo);
179
180 const auto* ph = dynamic_cast<const xAOD::Photon*>(eg);
181 if (!ph) return makeReject(m_acceptInfo);
182
183 return accept(ctx, ph);
184}
185
187 const xAOD::Photon* ph) const
188{
189 if (!ph) return makeReject(m_acceptInfo);
190 return acceptBDT(ctx, *ph, nullptr);
191}
192
194 const xAOD::Electron*) const
195{
196 // This tool is photon-only
197 return makeReject(m_acceptInfo);
198}
199
200
201StatusCode AsgPhotonBDTSelector::execute(const EventContext& ctx,
202 const xAOD::Egamma* eg,
203 unsigned int& isEM) const
204{
205 isEM = 0u;
206 if (!eg) return StatusCode::SUCCESS;;
207
208 const auto* ph = dynamic_cast<const xAOD::Photon*>(eg);
209 if (!ph) {
210 isEM = 1u; // or define a bit for wrong type
211 return StatusCode::SUCCESS;
212 }
213
214 (void) acceptBDT(ctx, *ph, &isEM);
215 return StatusCode::SUCCESS;
216}
217
218//=============================================================================
219// Helpers for cut applications
220//=============================================================================
224
225
226bool AsgPhotonBDTSelector::findBin(const float absEta, const float etGeV,
227 size_t& iEta, size_t& iEt) const {
228 // bins defined as [edge_i, edge_{i+1})
229 // Eta binning
230 auto itEta = std::upper_bound(m_etaBins.begin(), m_etaBins.end(), absEta);
231 if (itEta == m_etaBins.begin() || itEta == m_etaBins.end()) return false; // Eta out of range
232 iEta = (itEta - m_etaBins.begin()) - 1; // regular bin
233
234 // ET binning
235 auto itEt = std::upper_bound(m_etBinsGeV.begin(), m_etBinsGeV.end(), etGeV);
236 if (itEt == m_etBinsGeV.begin()) { iEt = 0; } // underflow: first bin
237 else if (itEt == m_etBinsGeV.end()) { iEt = m_etBinsGeV.size() - 2; } // overflow: last bin
238 else { iEt = (itEt - m_etBinsGeV.begin()) - 1; } // regular bin
239
240 return true;
241}
242
243float AsgPhotonBDTSelector::getCut(const bool converted, const size_t iEta, const size_t iEt) const {
244 const size_t nEta = m_etaBins.size() - 1;
245 const size_t idx = iEt * nEta + iEta;
246 const float cut = converted ? m_cutConv.at(idx) : m_cutUnconv.at(idx);
247 return cut;
248}
249
251 asg::AcceptData acc(&info);
252 for (unsigned i = 0; i < info.getNCuts(); ++i) acc.setCutResult(i, false);
253 return acc;
254}
255
257 float out = 0.f;
258 if (!ph.showerShapeValue(out, t)) {
259 ATH_MSG_ERROR("AsgPhotonBDTSelector: missing shower shape variable '" << name);
260 // Fail loudly
261 throw std::runtime_error(std::string("AsgPhotonBDTSelector: missing shower shape ") + name);
262 }
263 return out;
264}
265
266//=============================================================================
267// Accept method: apply cuts and return accept data
268//=============================================================================
269asg::AcceptData AsgPhotonBDTSelector::acceptBDT(const EventContext& ctx, const xAOD::Photon& ph, unsigned int* isEM) const {
270 // Helper for isEM word
271 auto setBit = [&](unsigned int bit) {
272 if (isEM) *isEM |= bit;
273 };
274 // I assume that the photon exists and is valid
275
276 // start to retrieve the acceptor
277 // Start with all cuts failed
279
280 // Ok now we assume that we have the score
282 const float score = decoratorScore(ph);
283 acc.setCutResult(m_cutPosHasScore, true);
284
285 // Now we check the photon kinematics from cluster and the binning
286 const xAOD::CaloCluster* cluster = ph.caloCluster();
287 if (!cluster) {
288 setBit(FailOutOfRange);
289 return acc;
290 }
291 const float absEta = std::abs(cluster->eta());
292 const float etGeV = cluster->pt() * 1e-3f;
293
294 size_t iEta=0, iEt=0;
295 if (!findBin(absEta, etGeV, iEta, iEt)) {
296 setBit(FailOutOfRange); // failOutOfRange
297 return acc;
298 }
299 // If we are here, the photon is in the correct eta range
300 acc.setCutResult(m_cutPosInRange, true);
301
302 // check if the photon is converted
303 const bool conv = isConverted(ph);
304
305 // Check F1 and e277 preselection cuts
306 bool passF1 = false, passE277 = false, passPre = false;
307 // Before trying to access the shower shape variables, we check if they are available.
308 // If not, we can either fail or reapply the WP based on the score and isEM word (if enabled and available)
310 float tmp = 0.f;
311 const bool hasF1 = ph.showerShapeValue(tmp, xAOD::EgammaParameters::f1);
312 const bool hasE277 = ph.showerShapeValue(tmp, xAOD::EgammaParameters::e277);
313 if (!hasF1 || !hasE277) {
314 // Check if isEM decoration is available
315 const SG::AuxElement::Accessor<int> accIsEM(m_isEMDecoration);
316 if (accIsEM.isAvailable(ph)) {
317 const int previousIsEM = accIsEM(ph);
318 passF1 = !(previousIsEM & FailPreselectionF1);
319 passE277 = !(previousIsEM & FailPreselectionE277);
320 passPre = passF1 && passE277;
321 }
322 else {
323 ATH_MSG_ERROR("Missing f1 and e277 shower shapes and isEM decoration, cannot reapply WP. Rejecting photon.");
324 acc.setCutResult(m_cutPosPreF1, false);
325 acc.setCutResult(m_cutPosPreE277, false);
326 acc.setCutResult(m_cutPosPassPreselection, false);
327 setBit(FailPreselectionF1);
328 setBit(FailPreselectionE277);
329 return acc;
330 }
331 }
332 }
333 else {
334 // If we are missing shower shapes and we are not reapplying the WP, we throw an error
335 float f1 = 0.f, e277 = 0.f;
338
339 const float cutF1 = conv ? m_cutF1Conv.at(0) : m_cutF1Unconv.at(0);
340 const float cutE277 = conv ? m_cutE277Conv.at(0) : m_cutE277Unconv.at(0);
341 passF1 = (f1 > cutF1);
342 passE277 = (e277 > cutE277);
343 passPre = passF1 && passE277;
344 }
345
346 // Decorate the accept data with the results of the preselection cuts
347 acc.setCutResult(m_cutPosPreF1, passF1);
348 acc.setCutResult(m_cutPosPreE277, passE277);
349 acc.setCutResult(m_cutPosPassPreselection, passPre);
350
351 // Set bits for failed preselections
352 if (!passF1) setBit(FailPreselectionF1);
353 if (!passE277) setBit(FailPreselectionE277);
354 // If failed preselection, reject and return
355 if (!passPre) return acc;
356
357 // Check the BDT score cut
358 const float cut = getCut(conv, iEta, iEt);
359 const bool passBDT = (score > cut);
360 acc.setCutResult(m_cutPosScore, (score > cut));
361 if (!passBDT) setBit(FailBDTScore);
362
363 return acc;
364}
365
366} // 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)
Handle class for reading a decoration on an object.
std::string PathResolverFindCalibFile(const std::string &logical_file_name)
std::enable_if_t< std::is_void_v< std::result_of_t< decltype(&T::renounce)(T)> > &&!std::is_base_of_v< SG::VarHandleKeyArray, T > &&std::is_base_of_v< Gaudi::DataHandle, T >, void > renounce(T &h)
Gaudi::Property< bool > m_suppressInputDeps
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
virtual const asg::AcceptInfo & getAcceptInfo() const override
Declare the interface ID for this pure-virtual interface class to the Athena framework.
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.
SG::ReadDecorHandleKey< xAOD::EgammaContainer > m_decoratorScore
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
SG::ReadHandleKey< xAOD::EgammaContainer > m_ContainerName
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
Handle class for reading a decoration on an object.
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".