ATLAS Offline Software
Loading...
Searching...
No Matches
FEAssociationTool.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
12#ifndef XAOD_STANDALONE
14#endif
15
16// EDM
22#include "xAODJet/Jet.h"
24
25#include <algorithm>
26#include <atomic>
27#include <functional>
28#include <memory>
29
30using namespace asg::msgUserCode;
31
32namespace
33{
34 constexpr float INV_GEV = 1.f / 1000.f;
35
36 static const SG::AuxElement::Accessor<ElementLink<xAOD::IParticleContainer>> ACC_partA("FE_ParticleA");
37 static const SG::AuxElement::Accessor<ElementLink<xAOD::IParticleContainer>> ACC_partB("FE_ParticleB");
38 static const SG::AuxElement::Accessor<int> ACC_typeA("FE_ParticleA_Type");
39 static const SG::AuxElement::Accessor<int> ACC_typeB("FE_ParticleB_Type");
40 static const SG::AuxElement::Accessor<float> ACC_sharedEc("FE_SharedEc");
41 static const SG::AuxElement::Accessor<float> ACC_sharedEn("FE_SharedEn");
42 static const SG::AuxElement::Accessor<float> ACC_fracAc("FE_FracAc");
43 static const SG::AuxElement::Accessor<float> ACC_fracAn("FE_FracAn");
44 static const SG::AuxElement::Accessor<float> ACC_fracBc("FE_FracBc");
45 static const SG::AuxElement::Accessor<float> ACC_fracBn("FE_FracBn");
46
47#ifndef XAOD_STANDALONE
48 using FELinks_t = std::vector<ElementLink<xAOD::FlowElementContainer>>;
49 using OrigObjLink_t = ElementLink<xAOD::IParticleContainer>;
50#else
51 static const SG::AuxElement::ConstAccessor<std::vector<ElementLink<xAOD::FlowElementContainer>>>
52 ACC_cFEs("chargedGlobalFELinks");
53 static const SG::AuxElement::ConstAccessor<std::vector<ElementLink<xAOD::FlowElementContainer>>>
54 ACC_nFEs("neutralGlobalFELinks");
55 static const SG::AuxElement::ConstAccessor<ElementLink<xAOD::IParticleContainer>>
56 ACC_origObj("originalObjectLink");
57#endif
58
59 inline void warnLimited(std::atomic<unsigned>& ctr, const std::function<void()>& fn)
60 {
61 if (ctr.fetch_add(1, std::memory_order_relaxed) < 10) fn();
62 }
63}
64
65namespace ORUtils
66{
67
70 const xAOD::IParticleContainer* b, std::size_t ib)
71{
72 const bool aFirst =
73 std::less<const xAOD::IParticleContainer*>{}(a, b) || (a == b && ia <= ib);
74
75 return aFirst ? PairKey{a, ia, b, ib} : PairKey{b, ib, a, ia};
76}
77
79{
80 return c1 == o.c1 && i1 == o.i1 && c2 == o.c2 && i2 == o.i2;
81}
82
83std::size_t FEAssociationTool::PairKeyHash::operator()(const PairKey& k) const noexcept
84{
85 std::size_t h1 = std::hash<const void*>{}(k.c1) ^ (std::hash<std::size_t>{}(k.i1) << 1);
86 std::size_t h2 = std::hash<const void*>{}(k.c2) ^ (std::hash<std::size_t>{}(k.i2) << 1);
87 return h1 ^ (h2 << 1);
88}
89
91 : asg::AsgTool(name)
92{}
93
95{
96#ifndef XAOD_STANDALONE
97 ATH_CHECK(m_elKey.initialize(SG::AllowEmpty));
98 ATH_CHECK(m_muKey.initialize(SG::AllowEmpty));
99 ATH_CHECK(m_phKey.initialize(SG::AllowEmpty));
100 ATH_CHECK(m_tauKey.initialize(SG::AllowEmpty));
101 ATH_CHECK(m_srjKey.initialize(SG::AllowEmpty));
102 ATH_CHECK(m_lrjKey.initialize(SG::AllowEmpty));
103
112 // You'd think we could use SG::AllowEmpty here, but
113 // `ReadDecorHandleKey` has a custom `initialize(bool)` override that
114 // hides the `initialize(AllowEmptyEnum)` method.
120#else
121 ATH_CHECK(m_elKey.initialize());
122 ATH_CHECK(m_muKey.initialize());
123 ATH_CHECK(m_phKey.initialize());
124 ATH_CHECK(m_tauKey.initialize());
125 ATH_CHECK(m_srjKey.initialize());
126 ATH_CHECK(m_lrjKey.initialize());
127#endif
128
129 ATH_CHECK(m_outputMapKey.initialize());
130
131 ANA_MSG_INFO("Initializing FEAssociationTool; OutputMap=" << m_outputMapKey.key());
132 return StatusCode::SUCCESS;
133}
134
135#ifndef XAOD_STANDALONE
136StatusCode FEAssociationTool::buildAssociations(const EventContext& ctx) const
137#else
139#endif
140{
141 ANA_CHECK_SET_TYPE(StatusCode);
142
143 std::vector<ObjView> objects;
144#ifndef XAOD_STANDALONE
145 ANA_CHECK(collectObjects(ctx, objects));
146#else
147 ANA_CHECK(collectObjects(objects));
148#endif
149
150 ANA_MSG_DEBUG("Collected " << objects.size() << " objects");
151
152#ifndef XAOD_STANDALONE
153 ANA_CHECK(buildMapFromPairs(ctx, objects));
154#else
156#endif
157
158 return StatusCode::SUCCESS;
159}
160
161#ifndef XAOD_STANDALONE
162StatusCode FEAssociationTool::collectObjects(const EventContext& ctx,
163 std::vector<ObjView>& objects) const
164#else
165StatusCode FEAssociationTool::collectObjects(std::vector<ObjView>& objects) const
166#endif
167{
168 auto collect_one = [&](ObjView::Type type, const xAOD::IParticleContainer* base)
169 {
170 if (!base) return;
171 for (std::size_t idx = 0; idx < base->size(); ++idx)
172 {
173 ObjView view(type, base, idx);
174#ifndef XAOD_STANDALONE
175 collectFEsFromIndex(ctx, type, base, idx, view);
176#else
177 collectFEsFromIndex(type, base, idx, view);
178#endif
179 objects.emplace_back(std::move(view));
180 }
181 };
182
183#ifndef XAOD_STANDALONE
184 {
186 if (h.isValid()) {
187 collect_one(ObjView::Type::Electron,
188 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
189 }
190 }
191 {
193 if (h.isValid()) {
194 collect_one(ObjView::Type::Muon,
195 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
196 }
197 }
198 {
200 if (h.isValid()) {
201 collect_one(ObjView::Type::Photon,
202 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
203 }
204 }
205 {
207 if (h.isValid()) {
208 collect_one(ObjView::Type::Tau,
209 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
210 }
211 }
212 {
214 if (h.isValid()) {
215 collect_one(ObjView::Type::SmallRJet,
216 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
217 }
218 }
219 {
221 if (h.isValid()) {
222 collect_one(ObjView::Type::LargeRJet,
223 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
224 }
225 }
226#else
227 {
229 if (h.isValid()) {
230 collect_one(ObjView::Type::Electron,
231 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
232 }
233 }
234 {
236 if (h.isValid()) {
237 collect_one(ObjView::Type::Muon,
238 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
239 }
240 }
241 {
243 if (h.isValid()) {
244 collect_one(ObjView::Type::Photon,
245 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
246 }
247 }
248 {
250 if (h.isValid()) {
251 collect_one(ObjView::Type::Tau,
252 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
253 }
254 }
255 {
257 if (h.isValid()) {
258 collect_one(ObjView::Type::SmallRJet,
259 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
260 }
261 }
262 {
264 if (h.isValid()) {
265 collect_one(ObjView::Type::LargeRJet,
266 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
267 }
268 }
269#endif
270
271 return StatusCode::SUCCESS;
272}
273
274#ifndef XAOD_STANDALONE
275void FEAssociationTool::collectFEsFromIndex(const EventContext& ctx,
276 const ObjView::Type type,
277 const xAOD::IParticleContainer* cont,
278 std::size_t idx,
279 ObjView& view) const
280#else
282 const xAOD::IParticleContainer* cont,
283 std::size_t idx,
284 ObjView& view) const
285#endif
286{
287 view.cSet.clear();
288 view.nSet.clear();
289 view.EcTot = 0.f;
290 view.EnTot = 0.f;
291
292 static std::atomic<unsigned> s_warn{0};
293
294 if (!cont)
295 {
296 warnLimited(s_warn, [&]{ ANA_MSG_WARNING("collectFEsFromIndex called with null container"); });
297 return;
298 }
299 if (idx >= cont->size())
300 {
301 warnLimited(s_warn, [&]{
302 ANA_MSG_WARNING("collectFEsFromIndex index out of range: idx=" << idx
303 << " size=" << cont->size());
304 });
305 return;
306 }
307
308 const xAOD::IParticle* p = (*cont)[idx];
309 if (!p)
310 {
311 warnLimited(s_warn, [&]{ ANA_MSG_WARNING("Null IParticle at idx=" << idx); });
312 return;
313 }
314
315 auto loop_links =
316 [&](const std::vector<ElementLink<xAOD::FlowElementContainer>>& links, bool charged)
317 {
318 for (const auto& el : links)
319 {
320 if (!el.isValid()) continue;
321
322 const xAOD::FlowElement* fe = *el;
323 if (!fe) continue;
324
325 if (charged) {
326 view.cSet.insert(fe);
327 view.EcTot += fe->e() * INV_GEV;
328 } else {
329 view.nSet.insert(fe);
330 view.EnTot += fe->e() * INV_GEV;
331 }
332 }
333 };
334
335 bool usedGlobal = false;
336
337#ifndef XAOD_STANDALONE
338 switch (type)
339 {
340 case ObjView::Type::Electron:
341 {
344 if (chargedFEs.isAvailable()) { usedGlobal = true; loop_links(chargedFEs(*p), true); }
345 if (neutralFEs.isAvailable()) { usedGlobal = true; loop_links(neutralFEs(*p), false); }
346 break;
347 }
348 case ObjView::Type::Muon:
349 {
352 if (chargedFEs.isAvailable()) { usedGlobal = true; loop_links(chargedFEs(*p), true); }
353 if (neutralFEs.isAvailable()) { usedGlobal = true; loop_links(neutralFEs(*p), false); }
354 break;
355 }
356 case ObjView::Type::Photon:
357 {
360 if (chargedFEs.isAvailable()) { usedGlobal = true; loop_links(chargedFEs(*p), true); }
361 if (neutralFEs.isAvailable()) { usedGlobal = true; loop_links(neutralFEs(*p), false); }
362 break;
363 }
364 case ObjView::Type::Tau:
365 {
368 if (chargedFEs.isAvailable()) { usedGlobal = true; loop_links(chargedFEs(*p), true); }
369 if (neutralFEs.isAvailable()) { usedGlobal = true; loop_links(neutralFEs(*p), false); }
370 break;
371 }
372 case ObjView::Type::SmallRJet:
373 {
374 // we can only create the handle if the key is non-empty
375 if (!m_srjChargedFELinksKey.empty()) {
377 if (chargedFEs.isAvailable()) { usedGlobal = true; loop_links(chargedFEs(*p), true); }
378 }
379 if (!m_srjNeutralFELinksKey.empty()) {
381 if (neutralFEs.isAvailable()) { usedGlobal = true; loop_links(neutralFEs(*p), false); }
382 }
383 break;
384 }
385 case ObjView::Type::LargeRJet:
386 {
387 // we can only create the handle if the key is non-empty
388 if (!m_lrjChargedFELinksKey.empty()) {
390 if (chargedFEs.isAvailable()) { usedGlobal = true; loop_links(chargedFEs(*p), true); }
391 }
392 if (!m_lrjNeutralFELinksKey.empty()) {
394 if (neutralFEs.isAvailable()) { usedGlobal = true; loop_links(neutralFEs(*p), false); }
395 }
396 break;
397 }
398 }
399#else
400 if (ACC_cFEs.isAvailable(*p)) {
401 usedGlobal = true;
402 loop_links(ACC_cFEs(*p), true);
403 }
404 if (ACC_nFEs.isAvailable(*p)) {
405 usedGlobal = true;
406 loop_links(ACC_nFEs(*p), false);
407 }
408#endif
409
410 if (!usedGlobal && p->type() == xAOD::Type::Jet)
411 {
412 const auto* jet = static_cast<const xAOD::Jet*>(p);
413
414 for (const auto& cl : jet->constituentLinks())
415 {
416 if (!cl.isValid()) continue;
417
418 const xAOD::IParticle* cpart = *cl;
419 if (!cpart) continue;
420 if (cpart->type() != xAOD::Type::FlowElement) continue;
421
422 const auto* constit = static_cast<const xAOD::FlowElement*>(cpart);
423
424#ifndef XAOD_STANDALONE
426
427 if (origObj.isAvailable())
428 {
429 const auto& feLink = origObj(*constit);
430#else
431 if (ACC_origObj.isAvailable(*constit))
432 {
433 const auto& feLink = ACC_origObj(*constit);
434#endif
435 if (!feLink.isValid()) continue;
436
437 const xAOD::IParticle* opart = *feLink;
438 if (!opart) continue;
439 if (opart->type() != xAOD::Type::FlowElement) continue;
440
441 const auto* fe = static_cast<const xAOD::FlowElement*>(opart);
442
443 if (fe->isCharged()) {
444 view.cSet.insert(fe);
445 view.EcTot += fe->e() * INV_GEV;
446 } else {
447 view.nSet.insert(fe);
448 view.EnTot += fe->e() * INV_GEV;
449 }
450 }
451 else
452 {
453 const auto* fe = constit;
454
455 if (fe->isCharged()) {
456 view.cSet.insert(fe);
457 view.EcTot += fe->e() * INV_GEV;
458 } else {
459 view.nSet.insert(fe);
460 view.EnTot += fe->e() * INV_GEV;
461 }
462 }
463 }
464 }
465}
466
467#ifndef XAOD_STANDALONE
468StatusCode FEAssociationTool::buildMapFromPairs(const EventContext& ctx,
469 const std::vector<ObjView>& objects) const
470#else
471StatusCode FEAssociationTool::buildMapFromPairs(const std::vector<ObjView>& objects)
472#endif
473{
474 std::unordered_map<PairKey, SharedAcc, PairKeyHash> shared;
475 shared.reserve(objects.size() * 2);
476
477 for (std::size_t i = 0; i < objects.size(); ++i)
478 {
479 const auto& A = objects[i];
480
481 for (std::size_t j = i + 1; j < objects.size(); ++j)
482 {
483 const auto& B = objects[j];
484
485 float Ec = 0.f;
486 float En = 0.f;
487
488 if (!A.cSet.empty() && !B.cSet.empty())
489 {
490 const auto& small = (A.cSet.size() < B.cSet.size()) ? A.cSet : B.cSet;
491 const auto& large = (A.cSet.size() < B.cSet.size()) ? B.cSet : A.cSet;
492 for (const auto* fe : small) {
493 if (large.count(fe)) Ec += fe->e() * INV_GEV;
494 }
495 }
496
497 if (!A.nSet.empty() && !B.nSet.empty())
498 {
499 const auto& small = (A.nSet.size() < B.nSet.size()) ? A.nSet : B.nSet;
500 const auto& large = (A.nSet.size() < B.nSet.size()) ? B.nSet : A.nSet;
501 for (const auto* fe : small) {
502 if (large.count(fe)) En += fe->e() * INV_GEV;
503 }
504 }
505
506 if (Ec <= 0.f && En <= 0.f) continue;
507
508 auto key = PairKey::make(A.cont, A.idx, B.cont, B.idx);
509 auto& s = shared[key];
510
511 s.Ec += Ec;
512 s.En += En;
513
514 s.AcTot = A.EcTot;
515 s.AnTot = A.EnTot;
516 s.BcTot = B.EcTot;
517 s.BnTot = B.EnTot;
518
519 s.typeA = static_cast<int>(A.type);
520 s.typeB = static_cast<int>(B.type);
521 }
522 }
523
524 auto assocMap = std::make_unique<xAOD::MissingETAssociationMap>();
525 auto assocAux = std::make_unique<xAOD::AuxContainerBase>();
526 assocMap->setStore(assocAux.get());
527
528 for (const auto& [key, sh] : shared)
529 {
530 auto* assoc = new xAOD::MissingETAssociation();
531 assocMap->push_back(assoc);
532
533 ElementLink<xAOD::IParticleContainer> linkA(*key.c1, key.i1);
534 ElementLink<xAOD::IParticleContainer> linkB(*key.c2, key.i2);
535
536 if (!linkA.isValid() || !linkB.isValid())
537 {
538 assocMap->pop_back();
539 delete assoc;
540 continue;
541 }
542
543 ACC_partA(*assoc) = linkA;
544 ACC_partB(*assoc) = linkB;
545 ACC_typeA(*assoc) = sh.typeA;
546 ACC_typeB(*assoc) = sh.typeB;
547
548 ACC_sharedEc(*assoc) = sh.Ec;
549 ACC_sharedEn(*assoc) = sh.En;
550
551 ACC_fracAc(*assoc) = (sh.AcTot > 0.f ? sh.Ec / sh.AcTot : 0.f);
552 ACC_fracAn(*assoc) = (sh.AnTot > 0.f ? sh.En / sh.AnTot : 0.f);
553 ACC_fracBc(*assoc) = (sh.BcTot > 0.f ? sh.Ec / sh.BcTot : 0.f);
554 ACC_fracBn(*assoc) = (sh.BnTot > 0.f ? sh.En / sh.BnTot : 0.f);
555 }
556
557#ifndef XAOD_STANDALONE
559 ATH_CHECK(mapH.record(std::move(assocMap), std::move(assocAux)));
560#else
561 ATH_CHECK(evtStore()->record(assocMap.release(), m_outputMapKey.key()));
562 ATH_CHECK(evtStore()->record(assocAux.release(), m_outputMapKey.key() + "Aux."));
563#endif
564
565 return StatusCode::SUCCESS;
566}
567
568} // namespace ORUtils
#define ATH_CHECK
Evaluate an expression and check for errors.
Handle class for reading from StoreGate.
Handle class for recording to StoreGate.
Base class for elements of a container that can have aux data.
macros for messaging and checking status codes
#define ANA_MSG_INFO(xmsg)
Macro printing info messages.
#define ANA_MSG_WARNING(xmsg)
Macro printing warning messages.
#define ANA_MSG_DEBUG(xmsg)
Macro printing debug messages.
#define ANA_CHECK(EXP)
check whether the given expression was successful
#define ANA_CHECK_SET_TYPE(TYPE)
set the type for ANA_CHECK to report failures
static Double_t a
Handle class for reading a decoration on an object.
ServiceHandle< StoreGateSvc > & evtStore()
FEAssociationTool(const std::string &name)
SG::ReadDecorHandleKey< xAOD::FlowElementContainer > m_originalObjectLinkKey
void collectFEsFromIndex(const EventContext &ctx, const ObjView::Type type, const xAOD::IParticleContainer *cont, std::size_t idx, ObjView &view) const
SG::ReadDecorHandleKey< xAOD::JetContainer > m_srjChargedFELinksKey
SG::WriteHandleKey< xAOD::MissingETAssociationMap > m_outputMapKey
SG::ReadDecorHandleKey< xAOD::PhotonContainer > m_phNeutralFELinksKey
virtual StatusCode collectObjects(const EventContext &ctx, std::vector< ObjView > &objects) const
SG::ReadHandleKey< xAOD::JetContainer > m_srjKey
SG::ReadDecorHandleKey< xAOD::PhotonContainer > m_phChargedFELinksKey
SG::ReadHandleKey< xAOD::MuonContainer > m_muKey
SG::ReadDecorHandleKey< xAOD::ElectronContainer > m_elNeutralFELinksKey
SG::ReadDecorHandleKey< xAOD::ElectronContainer > m_elChargedFELinksKey
SG::ReadHandleKey< xAOD::ElectronContainer > m_elKey
SG::ReadHandleKey< xAOD::TauJetContainer > m_tauKey
virtual StatusCode buildMapFromPairs(const EventContext &ctx, const std::vector< ObjView > &objects) const
SG::ReadDecorHandleKey< xAOD::TauJetContainer > m_tauNeutralFELinksKey
SG::ReadDecorHandleKey< xAOD::JetContainer > m_srjNeutralFELinksKey
SG::ReadDecorHandleKey< xAOD::TauJetContainer > m_tauChargedFELinksKey
virtual StatusCode buildAssociations(const EventContext &ctx) const override
SG::ReadHandleKey< xAOD::PhotonContainer > m_phKey
SG::ReadDecorHandleKey< xAOD::MuonContainer > m_muNeutralFELinksKey
SG::ReadHandleKey< xAOD::JetContainer > m_lrjKey
SG::ReadDecorHandleKey< xAOD::JetContainer > m_lrjChargedFELinksKey
SG::ReadDecorHandleKey< xAOD::JetContainer > m_lrjNeutralFELinksKey
SG::ReadDecorHandleKey< xAOD::MuonContainer > m_muChargedFELinksKey
virtual StatusCode initialize() override
Dummy implementation of the initialisation function.
Handle class for reading a decoration on an object.
bool isAvailable()
Test to see if this variable exists in the store, for the referenced object.
StatusCode record(std::unique_ptr< T > data)
Record a const object to the store.
AsgTool(const std::string &name)
Constructor specifying the tool instance's name.
Definition AsgTool.cxx:58
virtual double e() const override
The total energy of the particle.
Class providing the definition of the 4-vector interface.
virtual Type::ObjectType type() const =0
The type of the object as a simple enumeration.
std::string base
Definition hcg.cxx:83
@ Jet
The object is a jet.
Definition ObjectType.h:40
@ FlowElement
The object is a track-calo-cluster.
Definition ObjectType.h:52
Jet_v1 Jet
Definition of the current "jet version".
MissingETAssociation_v1 MissingETAssociation
Version control by type definition.
FlowElement_v1 FlowElement
Definition of the current "pfo version".
Definition FlowElement.h:16
DataVector< IParticle > IParticleContainer
Simple convenience declaration of IParticleContainer.
hold the test vectors and ease the comparison
std::size_t operator()(const PairKey &k) const noexcept
const xAOD::IParticleContainer * c2
static PairKey make(const xAOD::IParticleContainer *a, std::size_t ia, const xAOD::IParticleContainer *b, std::size_t ib)
const xAOD::IParticleContainer * c1
bool operator==(const PairKey &o) const noexcept