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
52 ACC_cFEs("chargedGlobalFELinks");
54 ACC_nFEs("neutralGlobalFELinks");
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 ANA_CHECK_SET_TYPE(StatusCode);
97
98#ifndef XAOD_STANDALONE
99 ATH_CHECK(m_elKey.initialize(SG::AllowEmpty));
100 ATH_CHECK(m_muKey.initialize(SG::AllowEmpty));
101 ATH_CHECK(m_phKey.initialize(SG::AllowEmpty));
102 ATH_CHECK(m_tauKey.initialize(SG::AllowEmpty));
103 ATH_CHECK(m_srjKey.initialize(SG::AllowEmpty));
104 ATH_CHECK(m_lrjKey.initialize(SG::AllowEmpty));
105
114 // You'd think we could use SG::AllowEmpty here, but
115 // `ReadDecorHandleKey` has a custom `initialize(bool)` override that
116 // hides the `initialize(AllowEmptyEnum)` method.
122#else
123 ATH_CHECK(m_elKey.initialize());
124 ATH_CHECK(m_muKey.initialize());
125 ATH_CHECK(m_phKey.initialize());
126 ATH_CHECK(m_tauKey.initialize());
127 ATH_CHECK(m_srjKey.initialize());
128 ATH_CHECK(m_lrjKey.initialize());
129#endif
130
131 ATH_CHECK(m_outputMapKey.initialize());
132
133 ANA_MSG_INFO("Initializing FEAssociationTool; OutputMap=" << m_outputMapKey.key());
134 return StatusCode::SUCCESS;
135}
136
137#ifndef XAOD_STANDALONE
138StatusCode FEAssociationTool::buildAssociations(const EventContext& ctx) const
139#else
141#endif
142{
143 ANA_CHECK_SET_TYPE(StatusCode);
144
145 std::vector<ObjView> objects;
146#ifndef XAOD_STANDALONE
147 ANA_CHECK(collectObjects(ctx, objects));
148#else
149 ANA_CHECK(collectObjects(objects));
150#endif
151
152 ANA_MSG_DEBUG("Collected " << objects.size() << " objects");
153
154#ifndef XAOD_STANDALONE
155 ANA_CHECK(buildMapFromPairs(ctx, objects));
156#else
158#endif
159
160 return StatusCode::SUCCESS;
161}
162
163#ifndef XAOD_STANDALONE
164StatusCode FEAssociationTool::collectObjects(const EventContext& ctx,
165 std::vector<ObjView>& objects) const
166#else
167StatusCode FEAssociationTool::collectObjects(std::vector<ObjView>& objects) const
168#endif
169{
170 ANA_CHECK_SET_TYPE(StatusCode);
171
172 auto collect_one = [&](ObjView::Type type, const xAOD::IParticleContainer* base)
173 {
174 if (!base) return;
175 for (std::size_t idx = 0; idx < base->size(); ++idx)
176 {
177 ObjView view(type, base, idx);
178#ifndef XAOD_STANDALONE
179 collectFEsFromIndex(ctx, type, base, idx, view);
180#else
181 collectFEsFromIndex(type, base, idx, view);
182#endif
183 objects.emplace_back(std::move(view));
184 }
185 };
186
187#ifndef XAOD_STANDALONE
188 {
190 if (h.isValid()) {
191 collect_one(ObjView::Type::Electron,
192 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
193 }
194 }
195 {
197 if (h.isValid()) {
198 collect_one(ObjView::Type::Muon,
199 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
200 }
201 }
202 {
204 if (h.isValid()) {
205 collect_one(ObjView::Type::Photon,
206 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
207 }
208 }
209 {
211 if (h.isValid()) {
212 collect_one(ObjView::Type::Tau,
213 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
214 }
215 }
216 {
218 if (h.isValid()) {
219 collect_one(ObjView::Type::SmallRJet,
220 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
221 }
222 }
223 {
225 if (h.isValid()) {
226 collect_one(ObjView::Type::LargeRJet,
227 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
228 }
229 }
230#else
231 {
233 if (h.isValid()) {
234 collect_one(ObjView::Type::Electron,
235 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
236 }
237 }
238 {
240 if (h.isValid()) {
241 collect_one(ObjView::Type::Muon,
242 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
243 }
244 }
245 {
247 if (h.isValid()) {
248 collect_one(ObjView::Type::Photon,
249 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
250 }
251 }
252 {
254 if (h.isValid()) {
255 collect_one(ObjView::Type::Tau,
256 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
257 }
258 }
259 {
261 if (h.isValid()) {
262 collect_one(ObjView::Type::SmallRJet,
263 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
264 }
265 }
266 {
268 if (h.isValid()) {
269 collect_one(ObjView::Type::LargeRJet,
270 static_cast<const xAOD::IParticleContainer*>(h.cptr()));
271 }
272 }
273#endif
274
275 return StatusCode::SUCCESS;
276}
277
278#ifndef XAOD_STANDALONE
279void FEAssociationTool::collectFEsFromIndex(const EventContext& ctx,
280 const ObjView::Type type,
281 const xAOD::IParticleContainer* cont,
282 std::size_t idx,
283 ObjView& view) const
284#else
286 const xAOD::IParticleContainer* cont,
287 std::size_t idx,
288 ObjView& view) const
289#endif
290{
291 view.cSet.clear();
292 view.nSet.clear();
293 view.EcTot = 0.f;
294 view.EnTot = 0.f;
295
296 static std::atomic<unsigned> s_warn{0};
297
298 if (!cont)
299 {
300 warnLimited(s_warn, [&]{ ANA_MSG_WARNING("collectFEsFromIndex called with null container"); });
301 return;
302 }
303 if (idx >= cont->size())
304 {
305 warnLimited(s_warn, [&]{
306 ANA_MSG_WARNING("collectFEsFromIndex index out of range: idx=" << idx
307 << " size=" << cont->size());
308 });
309 return;
310 }
311
312 const xAOD::IParticle* p = (*cont)[idx];
313 if (!p)
314 {
315 warnLimited(s_warn, [&]{ ANA_MSG_WARNING("Null IParticle at idx=" << idx); });
316 return;
317 }
318
319 auto loop_links =
320 [&](const std::vector<ElementLink<xAOD::FlowElementContainer>>& links, bool charged)
321 {
322 for (const auto& el : links)
323 {
324 if (!el.isValid()) continue;
325
326 const xAOD::FlowElement* fe = *el;
327 if (!fe) continue;
328
329 if (charged) {
330 view.cSet.insert(fe);
331 view.EcTot += fe->e() * INV_GEV;
332 } else {
333 view.nSet.insert(fe);
334 view.EnTot += fe->e() * INV_GEV;
335 }
336 }
337 };
338
339 bool usedGlobal = false;
340
341#ifndef XAOD_STANDALONE
342 switch (type)
343 {
344 case ObjView::Type::Electron:
345 {
348 if (chargedFEs.isAvailable()) { usedGlobal = true; loop_links(chargedFEs(*p), true); }
349 if (neutralFEs.isAvailable()) { usedGlobal = true; loop_links(neutralFEs(*p), false); }
350 break;
351 }
352 case ObjView::Type::Muon:
353 {
356 if (chargedFEs.isAvailable()) { usedGlobal = true; loop_links(chargedFEs(*p), true); }
357 if (neutralFEs.isAvailable()) { usedGlobal = true; loop_links(neutralFEs(*p), false); }
358 break;
359 }
360 case ObjView::Type::Photon:
361 {
364 if (chargedFEs.isAvailable()) { usedGlobal = true; loop_links(chargedFEs(*p), true); }
365 if (neutralFEs.isAvailable()) { usedGlobal = true; loop_links(neutralFEs(*p), false); }
366 break;
367 }
368 case ObjView::Type::Tau:
369 {
372 if (chargedFEs.isAvailable()) { usedGlobal = true; loop_links(chargedFEs(*p), true); }
373 if (neutralFEs.isAvailable()) { usedGlobal = true; loop_links(neutralFEs(*p), false); }
374 break;
375 }
376 case ObjView::Type::SmallRJet:
377 {
378 // we can only create the handle if the key is non-empty
379 if (!m_srjChargedFELinksKey.empty()) {
381 if (chargedFEs.isAvailable()) { usedGlobal = true; loop_links(chargedFEs(*p), true); }
382 }
383 if (!m_srjNeutralFELinksKey.empty()) {
385 if (neutralFEs.isAvailable()) { usedGlobal = true; loop_links(neutralFEs(*p), false); }
386 }
387 break;
388 }
389 case ObjView::Type::LargeRJet:
390 {
391 // we can only create the handle if the key is non-empty
392 if (!m_lrjChargedFELinksKey.empty()) {
394 if (chargedFEs.isAvailable()) { usedGlobal = true; loop_links(chargedFEs(*p), true); }
395 }
396 if (!m_lrjNeutralFELinksKey.empty()) {
398 if (neutralFEs.isAvailable()) { usedGlobal = true; loop_links(neutralFEs(*p), false); }
399 }
400 break;
401 }
402 }
403#else
404 if (ACC_cFEs.isAvailable(*p)) {
405 usedGlobal = true;
406 loop_links(ACC_cFEs(*p), true);
407 }
408 if (ACC_nFEs.isAvailable(*p)) {
409 usedGlobal = true;
410 loop_links(ACC_nFEs(*p), false);
411 }
412#endif
413
414 if (!usedGlobal && p->type() == xAOD::Type::Jet)
415 {
416 const auto* jet = static_cast<const xAOD::Jet*>(p);
417
418 for (const auto& cl : jet->constituentLinks())
419 {
420 if (!cl.isValid()) continue;
421
422 const xAOD::IParticle* cpart = *cl;
423 if (!cpart) continue;
424 if (cpart->type() != xAOD::Type::FlowElement) continue;
425
426 const auto* constit = static_cast<const xAOD::FlowElement*>(cpart);
427
428#ifndef XAOD_STANDALONE
430
431 if (origObj.isAvailable())
432 {
433 const auto& feLink = origObj(*constit);
434#else
435 if (ACC_origObj.isAvailable(*constit))
436 {
437 const auto& feLink = ACC_origObj(*constit);
438#endif
439 if (!feLink.isValid()) continue;
440
441 const xAOD::IParticle* opart = *feLink;
442 if (!opart) continue;
443 if (opart->type() != xAOD::Type::FlowElement) continue;
444
445 const auto* fe = static_cast<const xAOD::FlowElement*>(opart);
446
447 if (fe->isCharged()) {
448 view.cSet.insert(fe);
449 view.EcTot += fe->e() * INV_GEV;
450 } else {
451 view.nSet.insert(fe);
452 view.EnTot += fe->e() * INV_GEV;
453 }
454 }
455 else
456 {
457 const auto* fe = constit;
458
459 if (fe->isCharged()) {
460 view.cSet.insert(fe);
461 view.EcTot += fe->e() * INV_GEV;
462 } else {
463 view.nSet.insert(fe);
464 view.EnTot += fe->e() * INV_GEV;
465 }
466 }
467 }
468 }
469}
470
471#ifndef XAOD_STANDALONE
472StatusCode FEAssociationTool::buildMapFromPairs(const EventContext& ctx,
473 const std::vector<ObjView>& objects) const
474#else
475StatusCode FEAssociationTool::buildMapFromPairs(const std::vector<ObjView>& objects)
476#endif
477{
478 ANA_CHECK_SET_TYPE(StatusCode);
479
480 std::unordered_map<PairKey, SharedAcc, PairKeyHash> shared;
481 shared.reserve(objects.size() * 2);
482
483 for (std::size_t i = 0; i < objects.size(); ++i)
484 {
485 const auto& A = objects[i];
486
487 for (std::size_t j = i + 1; j < objects.size(); ++j)
488 {
489 const auto& B = objects[j];
490
491 float Ec = 0.f;
492 float En = 0.f;
493
494 if (!A.cSet.empty() && !B.cSet.empty())
495 {
496 const auto& small = (A.cSet.size() < B.cSet.size()) ? A.cSet : B.cSet;
497 const auto& large = (A.cSet.size() < B.cSet.size()) ? B.cSet : A.cSet;
498 for (const auto* fe : small) {
499 if (large.count(fe)) Ec += fe->e() * INV_GEV;
500 }
501 }
502
503 if (!A.nSet.empty() && !B.nSet.empty())
504 {
505 const auto& small = (A.nSet.size() < B.nSet.size()) ? A.nSet : B.nSet;
506 const auto& large = (A.nSet.size() < B.nSet.size()) ? B.nSet : A.nSet;
507 for (const auto* fe : small) {
508 if (large.count(fe)) En += fe->e() * INV_GEV;
509 }
510 }
511
512 if (Ec <= 0.f && En <= 0.f) continue;
513
514 auto key = PairKey::make(A.cont, A.idx, B.cont, B.idx);
515 auto& s = shared[key];
516
517 s.Ec += Ec;
518 s.En += En;
519
520 s.AcTot = A.EcTot;
521 s.AnTot = A.EnTot;
522 s.BcTot = B.EcTot;
523 s.BnTot = B.EnTot;
524
525 s.typeA = static_cast<int>(A.type);
526 s.typeB = static_cast<int>(B.type);
527 }
528 }
529
530 auto assocMap = std::make_unique<xAOD::MissingETAssociationMap>();
531 auto assocAux = std::make_unique<xAOD::AuxContainerBase>();
532 assocMap->setStore(assocAux.get());
533
534 for (const auto& [key, sh] : shared)
535 {
536 auto* assoc = new xAOD::MissingETAssociation();
537 assocMap->push_back(assoc);
538
539 ElementLink<xAOD::IParticleContainer> linkA(*key.c1, key.i1);
540 ElementLink<xAOD::IParticleContainer> linkB(*key.c2, key.i2);
541
542 if (!linkA.isValid() || !linkB.isValid())
543 {
544 assocMap->pop_back();
545 delete assoc;
546 continue;
547 }
548
549 ACC_partA(*assoc) = linkA;
550 ACC_partB(*assoc) = linkB;
551 ACC_typeA(*assoc) = sh.typeA;
552 ACC_typeB(*assoc) = sh.typeB;
553
554 ACC_sharedEc(*assoc) = sh.Ec;
555 ACC_sharedEn(*assoc) = sh.En;
556
557 ACC_fracAc(*assoc) = (sh.AcTot > 0.f ? sh.Ec / sh.AcTot : 0.f);
558 ACC_fracAn(*assoc) = (sh.AnTot > 0.f ? sh.En / sh.AnTot : 0.f);
559 ACC_fracBc(*assoc) = (sh.BcTot > 0.f ? sh.Ec / sh.BcTot : 0.f);
560 ACC_fracBn(*assoc) = (sh.BnTot > 0.f ? sh.En / sh.BnTot : 0.f);
561 }
562
563#ifndef XAOD_STANDALONE
565 ATH_CHECK(mapH.record(std::move(assocMap), std::move(assocAux)));
566#else
567 ATH_CHECK(evtStore()->record(assocMap.release(), m_outputMapKey.key()));
568 ATH_CHECK(evtStore()->record(assocAux.release(), m_outputMapKey.key() + "Aux."));
569#endif
570
571 return StatusCode::SUCCESS;
572}
573
574} // 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.
SG::ConstAccessor< T, ALLOC > ConstAccessor
Definition AuxElement.h:570
SG::Accessor< T, ALLOC > Accessor
Definition AuxElement.h:573
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:81
@ 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