ATLAS Offline Software
Loading...
Searching...
No Matches
MuonML::SegmentEdgeInferenceAlg Class Referencefinal

#include <SegmentEdgeInferenceAlg.h>

Inheritance diagram for MuonML::SegmentEdgeInferenceAlg:
Collaboration diagram for MuonML::SegmentEdgeInferenceAlg:

Classes

struct  TruthLossCounters
 Job-summed classification of lost truth segments (classifyLostTruthSegments()). More...
struct  GroupPurityCounters
 Job-summed grouping-purity diagnostics: does a formed (kept) component mix segments from more than one truth muon, and does each truth muon's retained segments end up in one clean component, a component shared with another muon, or split across several components? More...

Public Member Functions

StatusCode initialize () override
StatusCode execute (const EventContext &ctx) const override
StatusCode finalize () override
 Log of the post-ONNX rank/mutuality and min-component-size selection.
virtual StatusCode sysInitialize () override
 Override sysInitialize.
virtual bool isClonable () const override
 Specify if the algorithm is clonable.
virtual StatusCode sysExecute (const EventContext &ctx) override
 Execute an algorithm.
virtual const DataObjIDColl & extraOutputDeps () const override
 Return the list of extra output dependencies.
virtual bool filterPassed (const EventContext &ctx) const
 Get filter decision:
virtual void setFilterPassed (bool state, const EventContext &ctx) const
 Set filter decision:
ServiceHandle< StoreGateSvc > & evtStore ()
 The standard StoreGateSvc (event store) Returns (kind of) a pointer to the StoreGateSvc.
const ServiceHandle< StoreGateSvc > & detStore () const
 The standard StoreGateSvc/DetectorStore Returns (kind of) a pointer to the StoreGateSvc.
virtual StatusCode sysStart () override
 Handle START transition.
virtual std::vector< Gaudi::DataHandle * > inputHandles () const override
 Return this algorithm's input handles.
virtual std::vector< Gaudi::DataHandle * > outputHandles () const override
 Return this algorithm's output handles.
Gaudi::Details::PropertyBase & declareProperty (Gaudi::Property< T, V, H > &t)
void updateVHKA (Gaudi::Details::PropertyBase &)
MsgStream & msg () const
bool msgLvl (const MSG::Level lvl) const

Protected Member Functions

void renounceArray (SG::VarHandleKeyArray &handlesArray)
 remove all handles from I/O resolution
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)
void extraDeps_update_handler (Gaudi::Details::PropertyBase &ExtraDeps)
 Add StoreName to extra input/output deps as needed.

Private Types

typedef ServiceHandle< StoreGateSvc > StoreGateSvc_t

Private Member Functions

void classifyLostTruthSegments (const xAOD::MuonSegmentContainer &segments, const SegmentEdgeGraph &graph, const std::unordered_map< std::uint64_t, float > &pairProbability, const std::vector< unsigned char > &thresholded, const std::vector< std::uint8_t > &keptNode, const std::vector< unsigned char > &nodeDropReason) const
 MC-only: classify every truth-labeled input segment that did not make it into the filtered container by the stage that removed it.
void classifyGroupPurity (const std::unordered_map< std::int32_t, std::unordered_set< unsigned > > &muonComponents, const std::unordered_map< std::int32_t, bool > &muonEverMixed, const std::unordered_map< std::int32_t, double > &truthIdToAbsEta) const
 MC-only: given the per-muon component memberships and mixed-status gathered while the per-component classify every truth muon's final.
Gaudi::Details::PropertyBase & declareGaudiProperty (Gaudi::Property< T, V, H > &hndl, const SG::VarHandleKeyType &)
 specialization for handling Gaudi::Property<SG::VarHandleKey>

Private Attributes

SG::ReadHandleKey< xAOD::MuonSegmentContainer > m_segmentKey {this, "SegmentKey", "MsSegments"}
SG::WriteDecorHandleKey< xAOD::MuonSegmentContainer > m_pairGateDecorKey
 Per-segment payload consumed by MlMsTrackSeeder: [componentId, anchorRank + 1] Empty means that the segment was rejected by the ML preselection.
SG::WriteDecorHandleKey< xAOD::MuonSegmentContainer > m_componentScoreDecorKey
 Per-segment ML scores for MlMsTrackSeeder: [strongest selected cross-layer edge of the segment's component, strongest selected cross-layer edge of the segment itself] (-1 if none).
SG::WriteHandleKey< ConstDataVector< xAOD::MuonSegmentContainer > > m_filteredSegmentKey
Gaudi::Property< float > m_pairGateThreshold
Gaudi::Property< unsigned int > m_maxEdgesPerNode
Gaudi::Property< unsigned int > m_maxEdgesPerNodePerLayer
Gaudi::Property< bool > m_useDegreeCappedComponents
Gaudi::Property< bool > m_requireMutualTopKEdges
Gaudi::Property< bool > m_recoverOrphanNodes
Gaudi::Property< unsigned int > m_seedAnchorsPerComponent
Gaudi::Property< bool > m_anchorInnermostLayer
Gaudi::Property< bool > m_keepBestSegmentPerChamber
Gaudi::Property< unsigned int > m_minSegmentsPerComponent
ToolHandle< ISegmentEdgeClassifierTool > m_edgeClassifier {this, "EdgeClassifierTool", "MuonML::SegmentEdgeClassifierTool/SegmentEdgeClassifierTool"}
bool m_truthDiagEnabled {false}
 Whether truth-particle matching is available/enabled this job.
std::atomic< std::size_t > m_sumGraphNodes {0}
 Job-summed post-ONNX selection counters (see execute()).
std::atomic< std::size_t > m_sumThresholdedNodes {0}
std::atomic< std::size_t > m_sumMutualTopKPairs {0}
std::atomic< std::size_t > m_sumOneSidedTopKPairs {0}
std::atomic< std::size_t > m_sumOrphanRecoveryPairs {0}
std::atomic< std::size_t > m_sumTopologyNodes {0}
std::atomic< std::size_t > m_sumRetainedNodes {0}
std::atomic< std::size_t > m_sumChamberSuppressedNodes {0}
std::atomic< std::size_t > m_sumNodesRejectedByMinComponent {0}
std::atomic< std::size_t > m_sumRejectedComponents {0}
std::atomic< std::size_t > m_sumComponentsKept {0}
std::atomic< std::size_t > m_sumInputTruthSegments {0}
std::atomic< std::size_t > m_sumInputBkgSegments {0}
std::atomic< std::size_t > m_sumOnnxTruthSegments {0}
std::atomic< std::size_t > m_sumOnnxBkgSegments {0}
std::atomic< std::size_t > m_sumRetainedTruthSegments {0}
std::atomic< std::size_t > m_sumRetainedBkgSegments {0}
std::atomic< std::size_t > m_sumTrueEdgesTotal {0}
std::atomic< std::size_t > m_sumTrueEdgesPassed {0}
std::array< std::atomic< std::size_t >, 2 > m_sumTrueCrossLayerEdges ATLAS_THREAD_SAFE {}
 True cross-layer edges above PairGateThreshold, total and kept by the selection; barrel / endcap Every element is a std::atomic, updated only with atomic increments.
std::array< std::atomic< std::size_t >, 2 > m_sumTrueCrossLayerSelected ATLAS_THREAD_SAFE {}
std::atomic< std::size_t > m_sumBkgEdgesTotal {0}
std::atomic< std::size_t > m_sumBkgEdgesPassed {0}
TruthLossCounters m_truthLoss ATLAS_THREAD_SAFE
 Every element is a std::atomic, updated only with atomic additions.
std::atomic< bool > m_warnedMissingToolDiagnostics {false}
GroupPurityCounters m_groupPurity ATLAS_THREAD_SAFE
 Every element is a std::atomic, updated only with atomic additions/CAS.
DataObjIDColl m_extendedExtraObjects
 Extra output dependency collection, extended by AthAlgorithmDHUpdate to add symlinks.
StoreGateSvc_t m_evtStore
 Pointer to StoreGate (event store by default).
StoreGateSvc_t m_detStore
 Pointer to StoreGate (detector store by default).
std::vector< SG::VarHandleKeyArray * > m_vhka
bool m_varHandleArraysDeclared

Detailed Description

Definition at line 25 of file SegmentEdgeInferenceAlg.h.

Member Typedef Documentation

◆ StoreGateSvc_t

typedef ServiceHandle<StoreGateSvc> AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::StoreGateSvc_t
privateinherited

Definition at line 376 of file AthCommonDataStore.h.

Member Function Documentation

◆ classifyGroupPurity()

void MuonML::SegmentEdgeInferenceAlg::classifyGroupPurity ( const std::unordered_map< std::int32_t, std::unordered_set< unsigned > > & muonComponents,
const std::unordered_map< std::int32_t, bool > & muonEverMixed,
const std::unordered_map< std::int32_t, double > & truthIdToAbsEta ) const
private

MC-only: given the per-muon component memberships and mixed-status gathered while the per-component classify every truth muon's final.

Definition at line 912 of file SegmentEdgeInferenceAlg.cxx.

915 {
916 // Only muons with at least one retained segment in some kept component
917 // appear in muonComponents; fully-lost muons are already covered by
918 // classifyLostTruthSegments()/muonsSeedLost above, not double-counted here.
919 std::array<std::size_t, GroupPurityCounters::kColumns * 3> outcome{};
920 for (const auto& [id, componentIds] : muonComponents) {
921 const auto mixedIt = muonEverMixed.find(id);
922 const bool everMixed = mixedIt != muonEverMixed.end() && mixedIt->second;
923 // 0 = clean (one component, alone), 1 = mixed (shared a component with
924 // another muon), 2 = fragmented (split across components, never mixed).
925 const std::size_t result = everMixed ? 1 : (componentIds.size() >= 2 ? 2 : 0);
926 const auto etaIt = truthIdToAbsEta.find(id);
927 const std::size_t region = 1 + (etaIt != truthIdToAbsEta.end()
928 ? static_cast<std::size_t>(regionOf(etaIt->second)) : 0);
929 ++outcome[0 * 3 + result];
930 ++outcome[region * 3 + result];
931 }
932 for (std::size_t i = 0; i < outcome.size(); ++i) m_groupPurity.muonOutcome[i] += outcome[i];
933}

◆ classifyLostTruthSegments()

void MuonML::SegmentEdgeInferenceAlg::classifyLostTruthSegments ( const xAOD::MuonSegmentContainer & segments,
const SegmentEdgeGraph & graph,
const std::unordered_map< std::uint64_t, float > & pairProbability,
const std::vector< unsigned char > & thresholded,
const std::vector< std::uint8_t > & keptNode,
const std::vector< unsigned char > & nodeDropReason ) const
private

MC-only: classify every truth-labeled input segment that did not make it into the filtered container by the stage that removed it.

Called only when truth diagnostics are on.

Definition at line 733 of file SegmentEdgeInferenceAlg.cxx.

738 {
739 static_assert(kNumLossCategories == TruthLossCounters::kCategories);
740 constexpr std::size_t kColumns = TruthLossCounters::kColumns;
741 const std::size_t nInput = segments.size();
742
743 if (graph.inputNodeIndex.size() != nInput) {
744 if (!m_warnedMissingToolDiagnostics.exchange(true)) {
745 ATH_MSG_WARNING("Truth diagnostics requested, but the edge classifier tool "
746 "did not provide its pre-ONNX fates; set "
747 "EnableTruthDiagnostics on the tool as well. Skipping the "
748 "lost-segment classification.");
749 }
750 return;
751 }
752
753 // Truth particle of every input segment and the segments grouped per particle.
754 std::vector<const xAOD::TruthParticle*> truthPart(nInput, nullptr);
755 std::vector<std::int32_t> truthId(nInput, -1);
756 std::unordered_map<std::int32_t, std::vector<std::uint32_t>> byTruth;
757 {
758 std::uint32_t inputIndex = 0;
759 for (const xAOD::MuonSegment* segment : segments) {
760 if (const xAOD::TruthParticle* part = MuonR4::getTruthMatchedParticle(*segment)) {
761 truthPart[inputIndex] = part;
762 truthId[inputIndex] = static_cast<std::int32_t>(part->index());
763 byTruth[truthId[inputIndex]].push_back(inputIndex);
764 }
765 ++inputIndex;
766 }
767 }
768 const auto isRetained = [&](std::size_t input) {
769 const std::int32_t node = graph.inputNodeIndex[input];
770 return node >= 0 && keptNode[static_cast<std::size_t>(node)];
771 };
772
773 // Per segment: how far its best cross-chamber true pair got (1 = bucket cap,
774 // 2 = sector window, 3 = angle window, 4 = edge caps, 5 = scored, 0 = only
775 // same-chamber partners) and the score / index of its best scored pair.
776 const auto rankOf = [](PairGate gate) -> unsigned char {
777 switch (gate) {
778 case PairGate::BucketCap: return 1;
779 case PairGate::SectorWindow: return 2;
780 case PairGate::AngleWindow: return 3;
781 case PairGate::EdgeCaps: return 4;
782 case PairGate::Scored: return 5;
783 case PairGate::SameChamber: break;
784 }
785 return 0;
786 };
787 std::vector<unsigned char> bestRank(nInput, 0);
788 std::vector<float> bestScore(nInput, -1.f);
789 std::vector<std::int32_t> bestScoredPair(nInput, -1);
790 for (std::size_t k = 0; k < graph.truthPairFates.size(); ++k) {
791 const PairFate& fate = graph.truthPairFates[k];
792 const unsigned char rank = rankOf(fate.gate);
793 if (rank == 0) continue;
794 const std::array<std::uint32_t, 2> ends{fate.first, fate.second};
795 for (const std::uint32_t end : ends) bestRank[end] = std::max(bestRank[end], rank);
796 if (fate.gate != PairGate::Scored) continue;
797 const std::int32_t firstNode = graph.inputNodeIndex[fate.first];
798 const std::int32_t secondNode = graph.inputNodeIndex[fate.second];
799 if (firstNode < 0 || secondNode < 0) continue;
800 const auto found = pairProbability.find(undirectedPairKey(
801 static_cast<std::size_t>(firstNode), static_cast<std::size_t>(secondNode)));
802 const float score = found != pairProbability.end() ? found->second : 0.f;
803 for (const std::uint32_t end : ends) {
804 if (score > bestScore[end]) {
805 bestScore[end] = score;
806 bestScoredPair[end] = static_cast<std::int32_t>(k);
807 }
808 }
809 }
810
811 // Seedability per truth muon: at least 2 distinct chambers in the input,
812 // fewer than 2 among the retained segments.
813 std::unordered_map<std::int32_t, bool> seedLostByTruth;
814 std::array<std::size_t, 4> muonsSeedable{};
815 std::array<std::size_t, 4> muonsSeedLost{};
816 for (const auto& entry : byTruth) {
817 std::unordered_set<int> inputChambers;
818 std::unordered_set<int> retainedChambers;
819 for (const std::uint32_t input : entry.second) {
820 const int chamber = static_cast<int>(segments[input]->chamberIndex());
821 inputChambers.insert(chamber);
822 if (isRetained(input)) retainedChambers.insert(chamber);
823 }
824 const bool seedable = inputChambers.size() >= 2;
825 const bool lost = seedable && retainedChambers.size() < 2;
826 seedLostByTruth[entry.first] = lost;
827 if (!seedable) continue;
828 const std::size_t region = 1 + static_cast<std::size_t>(
829 regionOf(std::abs(truthPart[entry.second.front()]->eta())));
830 ++muonsSeedable[0];
831 ++muonsSeedable[region];
832 if (lost) {
833 ++muonsSeedLost[0];
834 ++muonsSeedLost[region];
835 }
836 }
837
838 std::array<std::size_t, kNumLossCategories * kColumns> category{};
839 std::array<std::size_t, 5> scoreBin{};
840 std::array<std::size_t, 2> sectorDelta{};
841 std::array<std::size_t, 4> layerPair{};
842 std::array<std::size_t, 4> muonSegments{};
843 std::array<std::size_t, 3> precisionHits{};
844 for (std::size_t i = 0; i < nInput; ++i) {
845 if (truthId[i] < 0 || isRetained(i)) continue;
846 const std::vector<std::uint32_t>& members = byTruth[truthId[i]];
847 const std::int32_t node = graph.inputNodeIndex[i];
848
849 std::size_t cat{};
850 if (members.size() == 1) {
851 cat = kNoPartnerSingleton;
852 } else if (bestRank[i] == 0) {
853 cat = kNoPartnerSameChamber;
854 } else if (node == kDroppedByBucketCap) {
855 cat = kBucketSelf;
856 } else if (node >= 0 && thresholded[static_cast<std::size_t>(node)]) {
857 switch (nodeDropReason[static_cast<std::size_t>(node)]) {
858 case kReasonChamberDedup: cat = kDedupChamber; break;
859 case kReasonComponentRejected: cat = kComponentRejected; break;
860 default: cat = kNotSelected; break;
861 }
862 } else {
863 switch (bestRank[i]) {
864 case 5: cat = kBelowThreshold; break;
865 case 4: cat = kEdgeCaps; break;
866 case 3: cat = kAngleWindow; break;
867 case 2: cat = kSectorWindow; break;
868 default: cat = kBucketPartners; break;
869 }
870 }
871
872 const std::size_t region = 1 + static_cast<std::size_t>(
873 regionOf(std::abs(truthPart[i]->eta())));
874 ++category[cat * kColumns];
875 ++category[cat * kColumns + region];
876 if (seedLostByTruth[truthId[i]]) ++category[cat * kColumns + 4];
877
878 if (cat != kBelowThreshold) continue;
879 if (bestScoredPair[i] >= 0) {
880 const PairFate& fate = graph.truthPairFates[static_cast<std::size_t>(bestScoredPair[i])];
881 const float score = bestScore[i];
882 ++scoreBin[score < 1e-3f ? 0 : score < 1e-2f ? 1 : score < 2e-2f ? 2
883 : score < 5e-2f ? 3 : 4];
884 ++sectorDelta[fate.sectorDelta == 0 ? 0 : 1];
885 const std::uint32_t other = fate.first == i ? fate.second : fate.first;
886 int rankA = layerRadialRank(Muon::MuonStationIndex::toLayerIndex(
887 segments[i]->chamberIndex()));
888 int rankB = layerRadialRank(Muon::MuonStationIndex::toLayerIndex(
889 segments[other]->chamberIndex()));
890 if (rankA > rankB) std::swap(rankA, rankB);
891 ++layerPair[(rankA == 0 && rankB == 1) ? 0
892 : (rankA == 1 && rankB == 2) ? 1
893 : (rankA == 0 && rankB == 2) ? 2 : 3];
894 }
895 ++muonSegments[std::min<std::size_t>(members.size(), 5) - 2];
896 const unsigned int hits = segments[i]->nPrecisionHits();
897 ++precisionHits[hits <= 4 ? 0 : (hits <= 6 ? 1 : 2)];
898 }
899
900 for (std::size_t c = 0; c < category.size(); ++c) m_truthLoss.category[c] += category[c];
901 for (std::size_t c = 0; c < scoreBin.size(); ++c) m_truthLoss.scoreBin[c] += scoreBin[c];
902 for (std::size_t c = 0; c < sectorDelta.size(); ++c) m_truthLoss.sectorDelta[c] += sectorDelta[c];
903 for (std::size_t c = 0; c < layerPair.size(); ++c) m_truthLoss.layerPair[c] += layerPair[c];
904 for (std::size_t c = 0; c < muonSegments.size(); ++c) m_truthLoss.muonSegments[c] += muonSegments[c];
905 for (std::size_t c = 0; c < precisionHits.size(); ++c) m_truthLoss.precisionHits[c] += precisionHits[c];
906 for (std::size_t c = 0; c < muonsSeedable.size(); ++c) {
907 m_truthLoss.muonsSeedable[c] += muonsSeedable[c];
908 m_truthLoss.muonsSeedLost[c] += muonsSeedLost[c];
909 }
910}
Scalar eta() const
pseudorapidity method
#define ATH_MSG_WARNING(x,...)
size_type size() const noexcept
Returns the number of elements in the collection.
PairGate
Gate that stopped (or did not stop) a pair of input segments from reaching ONNX.
@ Scored
the pair was sent to ONNX
@ SameChamber
same-chamber pair dropped by design
@ AngleWindow
cos(opening angle) below MaxDeltaThetaDeg
@ EdgeCaps
candidate evicted by the pre-ONNX per-node / per-target-chamber caps
@ SectorWindow
|delta sector| > MaxDeltaSector
@ BucketCap
an endpoint was dropped by MaxSegmentsPerBucket
constexpr std::int32_t kDroppedByBucketCap
Values of SegmentEdgeGraph::inputNodeIndex for input segments without a node.
const xAOD::TruthParticle * getTruthMatchedParticle(const xAOD::MuonSegment &segment)
Returns the particle truth-matched to the segment.
LayerIndex toLayerIndex(ChIndex index)
convert ChIndex into LayerIndex
void swap(ElementLinkVector< DOBJ > &lhs, ElementLinkVector< DOBJ > &rhs)
TruthParticle_v1 TruthParticle
Typedef to implementation.
MuonSegment_v1 MuonSegment
Reference the current persistent version:

◆ declareGaudiProperty()

Gaudi::Details::PropertyBase & AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::declareGaudiProperty ( Gaudi::Property< T, V, H > & hndl,
const SG::VarHandleKeyType &  )
inlineprivateinherited

specialization for handling Gaudi::Property<SG::VarHandleKey>

Definition at line 156 of file AthCommonDataStore.h.

158 {
160 hndl.value(),
161 hndl.documentation());
162
163 }
Gaudi::Details::PropertyBase & declareProperty(Gaudi::Property< T, V, H > &t)

◆ declareProperty()

Gaudi::Details::PropertyBase & AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::declareProperty ( Gaudi::Property< T, V, H > & t)
inlineinherited

Definition at line 145 of file AthCommonDataStore.h.

145 {
146 typedef typename SG::HandleClassifier<T>::type htype;
148 }
Gaudi::Details::PropertyBase & declareGaudiProperty(Gaudi::Property< T, V, H > &hndl, const SG::VarHandleKeyType &)
specialization for handling Gaudi::Property<SG::VarHandleKey>

◆ detStore()

const ServiceHandle< StoreGateSvc > & AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::detStore ( ) const
inlineinherited

The standard StoreGateSvc/DetectorStore Returns (kind of) a pointer to the StoreGateSvc.

Definition at line 95 of file AthCommonDataStore.h.

◆ evtStore()

ServiceHandle< StoreGateSvc > & AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::evtStore ( )
inlineinherited

The standard StoreGateSvc (event store) Returns (kind of) a pointer to the StoreGateSvc.

Definition at line 85 of file AthCommonDataStore.h.

◆ execute()

StatusCode MuonML::SegmentEdgeInferenceAlg::execute ( const EventContext & ctx) const
override

Definition at line 128 of file SegmentEdgeInferenceAlg.cxx.

128 {
129 const xAOD::MuonSegmentContainer* segments{};
130 ATH_CHECK(SG::get(segments, m_segmentKey, ctx));
131 // Evaluated once per event; short-circuits without touching the message
132 // service unless the property was explicitly enabled.
133 const bool truthDiag = m_truthDiagEnabled && msgLvl(MSG::DEBUG);
134 ATH_MSG_DEBUG("Event " << ctx.eventID().event_number()
135 << ": input segments in '" << m_segmentKey.key()
136 << "' = " << segments->size());
137
138 if (truthDiag) {
139 std::size_t truthSegs = 0, bkgSegs = 0;
140 for (const xAOD::MuonSegment* seg : *segments) {
141 MuonR4::getTruthMatchedParticle(*seg) ? ++truthSegs : ++bkgSegs;
142 }
143 m_sumInputTruthSegments += truthSegs;
144 m_sumInputBkgSegments += bkgSegs;
145 }
146
147 SegmentEdgeGraph graph{};
148 std::vector<SegmentEdgeScore> scores{};
149 ATH_CHECK(m_edgeClassifier->buildGraph(ctx, *segments, graph));
150 ATH_MSG_DEBUG("Event " << ctx.eventID().event_number()
151 << ": built graph with nodes=" << graph.nNodes
152 << ", edges=" << graph.nEdges);
153
154 // Per-node truth-particle grouping key (-1 = unlabeled), used for the
155 // segment-level funnel and for the edge-level true/background split.
156 std::vector<int32_t> nodeTruthId;
157 // Truth-particle id -> |eta|, filled alongside nodeTruthId.
158 std::unordered_map<int32_t, double> truthIdToAbsEta;
159 if (truthDiag) {
160 nodeTruthId.assign(graph.nNodes, -1);
161 std::size_t truthSegs = 0, bkgSegs = 0;
162 for (std::size_t node = 0; node < graph.nNodes; ++node) {
163 const xAOD::TruthParticle* truthPart =
164 MuonR4::getTruthMatchedParticle(*graph.segments[node]);
165 if (truthPart) {
166 nodeTruthId[node] = static_cast<int32_t>(truthPart->index());
167 truthIdToAbsEta.emplace(nodeTruthId[node], std::abs(truthPart->eta()));
168 ++truthSegs;
169 } else {
170 ++bkgSegs;
171 }
172 }
173 m_sumOnnxTruthSegments += truthSegs;
174 m_sumOnnxBkgSegments += bkgSegs;
175 }
176
177 ATH_CHECK(m_edgeClassifier->classifyEdges(ctx, graph, scores));
178 if (!scores.empty()) {
179 float minProb = std::numeric_limits<float>::max();
180 float maxProb = std::numeric_limits<float>::lowest();
181 for (const SegmentEdgeScore& score : scores) {
182 minProb = std::min(minProb, score.probability);
183 maxProb = std::max(maxProb, score.probability);
184 }
185 ATH_MSG_DEBUG("Event " << ctx.eventID().event_number()
186 << ": edge scores=" << scores.size()
187 << ", prob range=[" << minProb << ", " << maxProb << "]");
188 } else {
189 ATH_MSG_DEBUG("Event " << ctx.eventID().event_number()
190 << ": no edge scores produced");
191 }
192
193 // Symmetrise directed model outputs once. Everything downstream uses the
194 // score of an undirected segment association.
195 std::unordered_map<std::uint64_t, float> pairProbability;
196 pairProbability.reserve(scores.size());
197 for (const SegmentEdgeScore& score : scores) {
198 if (score.src >= graph.nNodes || score.dst >= graph.nNodes ||
199 score.src == score.dst) {
200 continue;
201 }
202 const std::uint64_t key = undirectedPairKey(score.src, score.dst);
203 auto [it, inserted] = pairProbability.emplace(key, score.probability);
204 if (!inserted) it->second = std::max(it->second, score.probability);
205 }
206
207 // Edge-level recall/false-positive check: candidate pairs that reached ONNX
208 // true pairs reach PairGateThreshold, versus how many background pairs do.
209 if (truthDiag) {
210 std::size_t trueTotal = 0, truePassed = 0, bkgTotal = 0, bkgPassed = 0;
211 for (const auto& [key, probability] : pairProbability) {
212 const auto [first, second] = unpackPairKey(key);
213 if (first >= graph.nNodes || second >= graph.nNodes) continue;
214 const bool isTrueEdge =
215 nodeTruthId[first] >= 0 && nodeTruthId[first] == nodeTruthId[second];
216 const bool passed = probability >= m_pairGateThreshold.value();
217 if (isTrueEdge) {
218 ++trueTotal;
219 truePassed += passed;
220 } else {
221 ++bkgTotal;
222 bkgPassed += passed;
223 }
224 }
225 m_sumTrueEdgesTotal += trueTotal;
226 m_sumTrueEdgesPassed += truePassed;
227 m_sumBkgEdgesTotal += bkgTotal;
228 m_sumBkgEdgesPassed += bkgPassed;
229 }
230
231 // Form a sparse, score-ranked topology. Mutual top-K associations retain
232 // locally consistent paths while suppressing one-sided bridges.
233 using WeightedEdge = std::pair<std::uint64_t, float>;
234 const auto betterWeightedEdge = [](const WeightedEdge& first,
235 const WeightedEdge& second) {
236 const int probabilityOrder = InferenceUtils::compareFloatDescending(
237 first.second, second.second);
238 if (probabilityOrder != 0) {
239 return probabilityOrder < 0;
240 }
241 return first.first < second.first;
242 };
243 std::vector<std::vector<WeightedEdge>> edgesByNode(graph.nNodes);
244 std::size_t thresholdPairs = 0;
245 for (const auto& [key, probability] : pairProbability) {
246 if (probability < m_pairGateThreshold.value()) continue;
247 const auto [first, second] = unpackPairKey(key);
248 if (first >= graph.nNodes || second >= graph.nNodes) continue;
249 edgesByNode[first].emplace_back(key, probability);
250 edgesByNode[second].emplace_back(key, probability);
251 ++thresholdPairs;
252 }
253
254 std::size_t thresholdedNodes = 0;
255 for (const std::vector<WeightedEdge>& nodeEdges : edgesByNode) {
256 thresholdedNodes += !nodeEdges.empty();
257 }
258
259 std::vector<Muon::MuonStationIndex::LayerIndex> nodeLayer(graph.nNodes);
260 for (std::size_t node = 0; node < graph.nNodes; ++node) {
261 nodeLayer[node] = Muon::MuonStationIndex::toLayerIndex(graph.segments[node]->chamberIndex());
262 }
263 const unsigned int maxEdgesPerLayer = m_maxEdgesPerNodePerLayer.value();
264 std::unordered_map<std::uint64_t, unsigned char> nominations;
265 nominations.reserve(thresholdPairs);
266 for (std::size_t node = 0; node < graph.nNodes; ++node) {
267 std::vector<WeightedEdge>& nodeEdges = edgesByNode[node];
268 std::sort(nodeEdges.begin(), nodeEdges.end(), betterWeightedEdge);
269 if (maxEdgesPerLayer != 0) {
270 // Best edges towards each layer: a segment keeps its partner in every neighbouring
271 // layer, and same-layer edges do not compete with cross-layer ones.
272 std::array<unsigned int, Acts::toUnderlying(Muon::MuonStationIndex::LayerIndex::LayerIndexMax) + 1> taken{};
273 std::erase_if(nodeEdges, [&](const WeightedEdge& edge) {
274 const auto [first, second] = unpackPairKey(edge.first);
275 const std::size_t other = first == node ? second : first;
276 const auto layer = static_cast<std::size_t>(Acts::toUnderlying(nodeLayer[other]) + 1);
277 return taken[layer]++ >= maxEdgesPerLayer;
278 });
279 } else if (m_maxEdgesPerNode.value() != 0 &&
280 nodeEdges.size() > m_maxEdgesPerNode.value()) {
281 nodeEdges.resize(m_maxEdgesPerNode.value());
282 }
283 for (const WeightedEdge& edge : nodeEdges) {
284 ++nominations[edge.first];
285 }
286 }
287
288 std::size_t mutualTopKPairs = 0;
289 std::size_t oneSidedTopKPairs = 0;
290 for (const auto& [_, count] : nominations) {
291 if (count == 2) {
292 ++mutualTopKPairs;
293 } else {
294 ++oneSidedTopKPairs;
295 }
296 }
297
298 std::unordered_set<std::uint64_t> selectedPairKeys;
299 selectedPairKeys.reserve(thresholdPairs);
300 if (m_useDegreeCappedComponents.value()) {
301 // Apply a score-ordered global degree cap when explicitly requested.
302 std::vector<WeightedEdge> acceptedPairs;
303 acceptedPairs.reserve(thresholdPairs);
304 for (const auto& [key, probability] : pairProbability) {
305 if (probability < m_pairGateThreshold.value()) continue;
306 acceptedPairs.emplace_back(key, probability);
307 }
308 std::sort(acceptedPairs.begin(), acceptedPairs.end(), betterWeightedEdge);
309
310 const unsigned int maxDegree = m_maxEdgesPerNode.value();
311 std::vector<unsigned int> degree(graph.nNodes, 0);
312 for (const WeightedEdge& edge : acceptedPairs) {
313 const auto [first, second] = unpackPairKey(edge.first);
314 if (maxDegree != 0 &&
315 (degree[first] >= maxDegree || degree[second] >= maxDegree)) {
316 continue;
317 }
318 selectedPairKeys.insert(edge.first);
319 ++degree[first];
320 ++degree[second];
321 }
322 } else {
323 for (const auto& [key, count] : nominations) {
324 if (m_requireMutualTopKEdges.value() && count != 2) continue;
325 selectedPairKeys.insert(key);
326 }
327 }
328
329 // A mutual top-K selection can leave an endpoint without an association.
330 // Add its best thresholded edge, at most once per orphaned endpoint.
331 std::size_t orphanRecoveryPairs = 0;
332 if (!m_useDegreeCappedComponents.value() &&
333 m_requireMutualTopKEdges.value() &&
334 m_recoverOrphanNodes.value()) {
335 std::vector<unsigned char> selectedNode(graph.nNodes, 0);
336 for (const std::uint64_t key : selectedPairKeys) {
337 const auto [first, second] = unpackPairKey(key);
338 if (first < graph.nNodes) selectedNode[first] = 1;
339 if (second < graph.nNodes) selectedNode[second] = 1;
340 }
341 for (std::size_t node = 0; node < graph.nNodes; ++node) {
342 if (selectedNode[node] || edgesByNode[node].empty()) continue;
343 const std::uint64_t key = edgesByNode[node].front().first;
344 const auto [first, second] = unpackPairKey(key);
345 if (first >= graph.nNodes || second >= graph.nNodes) continue;
346 if (selectedPairKeys.insert(key).second) ++orphanRecoveryPairs;
347 selectedNode[first] = 1;
348 selectedNode[second] = 1;
349 }
350 }
351
352 std::vector<std::uint64_t> selectedPairs{selectedPairKeys.begin(),
353 selectedPairKeys.end()};
354 std::sort(selectedPairs.begin(), selectedPairs.end());
355
356 // Truth diagnostics only: true cross-layer edges above threshold that the selection keeps,
357 // barrel / endcap (by the first endpoint's chamber)
358 if (truthDiag) {
359 for (const auto& [key, probability] : pairProbability) {
360 if (probability < m_pairGateThreshold.value()) continue;
361 const auto [first, second] = unpackPairKey(key);
362 if (first >= graph.nNodes || second >= graph.nNodes) continue;
363 if (nodeTruthId[first] < 0 || nodeTruthId[first] != nodeTruthId[second]) continue;
364 if (nodeLayer[first] == nodeLayer[second]) continue;
365 const std::size_t region =
366 Muon::MuonStationIndex::isBarrel(graph.segments[first]->chamberIndex()) ? 0 : 1;
367 ++m_sumTrueCrossLayerEdges[region];
368 if (selectedPairKeys.contains(key)) ++m_sumTrueCrossLayerSelected[region];
369 }
370 }
371
372 // Truth diagnostics only: best selected-edge score per node, so that the
373 // strongest edge holding a component together is a max over its nodes.
374 std::vector<float> nodeBestSelectedScore;
375 if (truthDiag) {
376 nodeBestSelectedScore.assign(graph.nNodes, -1.f);
377 for (const std::uint64_t key : selectedPairs) {
378 const auto found = pairProbability.find(key);
379 if (found == pairProbability.end()) continue;
380 const auto [first, second] = unpackPairKey(key);
381 nodeBestSelectedScore[first] = std::max(nodeBestSelectedScore[first], found->second);
382 nodeBestSelectedScore[second] = std::max(nodeBestSelectedScore[second], found->second);
383 }
384 }
385
386 // Innermost-layer anchoring with an anchor cap: rank the inner candidates by
387 // their strongest selected edge towards another layer, i.e. by how firmly
388 // the model links them outwards. Same-chamber alternatives otherwise all
389 // launch seeds built from the same outer segments.
390 const auto layerOf = [&graph](std::size_t node) {
391 return Muon::MuonStationIndex::toLayerIndex(graph.segments[node]->chamberIndex());
392 };
393 std::vector<float> nodeBestCrossLayerScore;
394 std::vector<unsigned int> nodeCrossLayerEdges; // truth diagnostics only
395 {
396 nodeBestCrossLayerScore.assign(graph.nNodes, -1.f);
397 if (truthDiag) nodeCrossLayerEdges.assign(graph.nNodes, 0);
398 for (const std::uint64_t key : selectedPairs) {
399 const auto [first, second] = unpackPairKey(key);
400 if (layerOf(first) == layerOf(second)) continue;
401 const auto found = pairProbability.find(key);
402 if (found == pairProbability.end()) continue;
403 nodeBestCrossLayerScore[first] = std::max(nodeBestCrossLayerScore[first], found->second);
404 nodeBestCrossLayerScore[second] = std::max(nodeBestCrossLayerScore[second], found->second);
405 if (truthDiag) {
406 ++nodeCrossLayerEdges[first];
407 ++nodeCrossLayerEdges[second];
408 }
409 }
410 }
411
412
413 DisjointSet components{graph.nNodes};
414 std::vector<bool> activeNode(graph.nNodes, false);
415 for (const std::uint64_t key : selectedPairs) {
416 const auto [first, second] = unpackPairKey(key);
417 components.unite(first, second);
418 activeNode[first] = true;
419 activeNode[second] = true;
420 }
421
422 std::unordered_map<std::size_t, std::vector<std::size_t>> byRoot;
423 byRoot.reserve(graph.nNodes);
424 for (std::size_t node = 0; node < graph.nNodes; ++node) {
425 if (activeNode[node]) byRoot[components.find(node)].push_back(node);
426 }
427
428 // Deterministic component IDs make debugging and validation reproducible.
429 std::vector<std::vector<std::size_t>> componentNodes;
430 componentNodes.reserve(byRoot.size());
431 for (auto& [_, nodes] : byRoot) {
432 std::sort(nodes.begin(), nodes.end());
433 componentNodes.push_back(std::move(nodes));
434 }
435 std::ranges::sort(componentNodes,
436 [](const auto& first, const auto& second) {
437 return first.front() < second.front();
438 });
439
440 xAOD::ContainerDecorator<xAOD::MuonSegmentContainer, std::vector<unsigned>>
441 decor{m_pairGateDecorKey, ctx};
442 xAOD::ContainerDecorator<xAOD::MuonSegmentContainer, std::vector<float>>
443 scoreDecor{m_componentScoreDecorKey, ctx};
444
445 std::vector<std::uint8_t> keptNode(graph.nNodes, false);
446 std::vector<std::uint8_t> nodeDropReason;
447 if (truthDiag) {
448 nodeDropReason.assign(graph.nNodes, kReasonNotSelected);
449 }
450
451 // Group-purity: which kept-component id(s) each truth muon's
452 // segments landed in, and whether any of those components were mixed
453 std::unordered_map<int32_t, std::unordered_set<unsigned>> muonComponents;
454 std::unordered_map<int32_t, bool> muonEverMixed;
455 // D8: weak clean components and, per truth muon, the number of strong multi-layer components
456 std::vector<int32_t> weakCleanMuons;
457 std::unordered_map<int32_t, unsigned int> strongComponentsPerMuon;
458 const auto sizeBin = [](std::size_t size) -> std::size_t {
459 if (size <= 4) return size - 2; // 2, 3, 4 -> 0, 1, 2
460 if (size < 10) return 3; // 5-9
461 if (size < 20) return 4; // 10-19
462 return 5; // 20+
463 };
464
465 std::size_t topologyNodes = 0;
466 std::size_t retainedNodes = 0;
467 std::size_t chamberSuppressedNodes = 0;
468 std::size_t rejectedComponents = 0;
469 std::size_t componentsKept = 0;
470 std::size_t anchors = 0;
471 std::size_t nodesRejectedByMinComponent = 0;
472 unsigned nextComponentId = 1;
473 const InferenceUtils::SegmentQualityOrder betterSegment{};
474
475 const auto isBetterNode = [&](std::size_t candidate, std::size_t incumbent) {
476 if (betterSegment(graph.segments[candidate], graph.segments[incumbent])) {
477 return true;
478 }
479 if (betterSegment(graph.segments[incumbent], graph.segments[candidate])) {
480 return false;
481 }
482 return candidate < incumbent;
483 };
484
485 for (const std::vector<std::size_t>& rawNodes : componentNodes) {
486 topologyNodes += rawNodes.size();
487 std::vector<std::size_t> retained = rawNodes;
488
489 // The seeder resolves same-chamber alternatives while building a seed.
490 // Retaining only the highest-ranked representative is therefore optional.
491 if (m_keepBestSegmentPerChamber.value()) {
492 std::unordered_map<int, std::size_t> bestByChamber;
493 bestByChamber.reserve(rawNodes.size());
494 for (const std::size_t node : rawNodes) {
495 const int chamber =
496 static_cast<int>(graph.segments[node]->chamberIndex());
497 const auto found = bestByChamber.find(chamber);
498 if (found == bestByChamber.end() || isBetterNode(node, found->second)) {
499 bestByChamber[chamber] = node;
500 }
501 }
502 retained.clear();
503 retained.reserve(bestByChamber.size());
504 for (const auto& [_, node] : bestByChamber) retained.push_back(node);
505 std::sort(retained.begin(), retained.end());
506 chamberSuppressedNodes += rawNodes.size() - retained.size();
507 }
508
509 // Provisional reasons; nodes that end up kept are identified by keptNode.
510 if (truthDiag) {
511 for (const std::size_t node : rawNodes) {
512 nodeDropReason[node] = kReasonChamberDedup;
513 }
514 for (const std::size_t node : retained) {
515 nodeDropReason[node] = kReasonComponentRejected;
516 }
517 }
518
519 if (retained.size() < m_minSegmentsPerComponent.value()) {
520 nodesRejectedByMinComponent += retained.size();
521 ++rejectedComponents;
522 continue;
523 }
524
525 // Only ranked component members launch seeds. The edge score therefore
526 // reduces seed attempts directly rather than serving only as a label.
527 std::vector<std::size_t> rankedNodes{retained};
528 if (m_anchorInnermostLayer.value()) {
529 // Anchor only on the inner segment(s)
530 int bestRank = std::numeric_limits<int>::max();
531 for (const std::size_t node : rankedNodes) {
532 bestRank = std::min(
533 bestRank,
535 graph.segments[node]->chamberIndex())));
536 }
537 std::erase_if(rankedNodes, [&](std::size_t node) {
538 return layerRadialRank(Muon::MuonStationIndex::toLayerIndex(
539 graph.segments[node]->chamberIndex())) != bestRank;
540 });
541 // Rank the inner anchors (strongest cross-layer edge first, segment quality as
542 // tie-break) and cap them.
543 if (m_seedAnchorsPerComponent.value() != 0) {
544 std::ranges::sort(rankedNodes, [&](std::size_t first, std::size_t second) {
546 nodeBestCrossLayerScore[first], nodeBestCrossLayerScore[second]);
547 return order != 0 ? order < 0 : isBetterNode(first, second);
548 });
549 if (rankedNodes.size() > m_seedAnchorsPerComponent.value()) {
550 rankedNodes.resize(m_seedAnchorsPerComponent.value());
551 }
552 }
553 } else {
554 std::ranges::sort(rankedNodes, isBetterNode);
555 const std::size_t nAnchors = m_seedAnchorsPerComponent.value() == 0
556 ? rankedNodes.size()
557 : std::min<std::size_t>(m_seedAnchorsPerComponent.value(),
558 rankedNodes.size());
559 rankedNodes.resize(nAnchors);
560 }
561 if (rankedNodes.empty()) {
562 ++rejectedComponents;
563 continue;
564 }
565 // rankedNodes stays in rank order (best anchor first): the decoration
566 // carries the rank so that the seeder can try anchors best-first.
567
568 const unsigned componentId = nextComponentId++;
569 float componentCrossScore = -1.f;
570 for (const std::size_t node : rawNodes) {
571 componentCrossScore = std::max(componentCrossScore, nodeBestCrossLayerScore[node]);
572 }
573 for (const std::size_t node : retained) {
574 scoreDecor(*graph.segments[node]) = {componentCrossScore, nodeBestCrossLayerScore[node]};
575 const auto anchorIt = std::ranges::find(rankedNodes, node);
576 const bool isAnchor = anchorIt != rankedNodes.end();
577 decor(*graph.segments[node]) = {
578 componentId,
579 isAnchor ? static_cast<unsigned int>(anchorIt - rankedNodes.begin()) + 1 : 0u};
580 keptNode[node] = true;
581 }
582 retainedNodes += retained.size();
583 anchors += rankedNodes.size();
584 ++componentsKept;
585
586 // Group purity: does this kept segments from more than one truth muon?
587 if (truthDiag) {
588 std::unordered_set<int32_t> componentTruthIds;
589 for (const std::size_t node : retained) {
590 if (nodeTruthId[node] >= 0) componentTruthIds.insert(nodeTruthId[node]);
591 }
592 const std::size_t outcome = componentTruthIds.empty() ? 2
593 : componentTruthIds.size() == 1 ? 0 : 1; // clean/mixed/bkgOnly
594 ++m_groupPurity.componentsByOutcome[outcome];
595 ++m_groupPurity.componentSizeHist[sizeBin(retained.size())];
596
597 // Component descriptors that a component-level cut could use: the
598 // strongest selected edge among the nodes that formed it (before the
599 // chamber dedup), its retained size and its number of distinct layers.
600 float strongest = -1.f;
601 for (const std::size_t node : rawNodes) {
602 strongest = std::max(strongest, nodeBestSelectedScore[node]);
603 }
604 const std::size_t scoreBin = strongest < 0.05f ? 0 : strongest < 0.1f ? 1
605 : strongest < 0.3f ? 2 : strongest < 0.5f ? 3 : 4;
606 std::uint32_t layerMask = 0;
607 for (const std::size_t node : retained) {
608 // +1 so that LayerUnknown (-1) maps onto bit 0
609 layerMask |= 1u << (Acts::toUnderlying(Muon::MuonStationIndex::toLayerIndex(
610 graph.segments[node]->chamberIndex())) + 1);
611 }
612 const std::size_t layerBin =
613 std::min<std::size_t>(std::popcount(layerMask), GroupPurityCounters::kLayerBins) - 1;
614 ++m_groupPurity.scoreByOutcome[outcome * GroupPurityCounters::kScoreBins + scoreBin];
615 ++m_groupPurity.sizeByOutcome[outcome * GroupPurityCounters::kSizeBins + sizeBin(retained.size())];
616 ++m_groupPurity.layersByOutcome[outcome * GroupPurityCounters::kLayerBins + layerBin];
617
618 // Components that can seed (>= 2 layers)
619 if (std::popcount(layerMask) >= 2) {
620 float crossScore = -1.f;
621 unsigned int crossEdges = 0;
622 for (const std::size_t node : rawNodes) {
623 crossScore = std::max(crossScore, nodeBestCrossLayerScore[node]);
624 crossEdges += nodeCrossLayerEdges[node];
625 }
626 crossEdges /= 2;
627 const std::size_t crossBin = crossScore < 0.05f ? 0 : crossScore < 0.1f ? 1 : crossScore < 0.3f ? 2
628 : crossScore < 0.5f ? 3 : crossScore < 0.8f ? 4 : 5;
629 ++m_groupPurity.crossScoreByOutcome[outcome * GroupPurityCounters::kCrossScoreBins + crossBin];
630 ++m_groupPurity.crossEdgesByOutcome[outcome * GroupPurityCounters::kCrossEdgeBins +
631 std::min<std::size_t>(crossEdges, 3)];
632 if (crossScore < 0.05f) {
633 if (outcome == 0) weakCleanMuons.push_back(*componentTruthIds.begin());
634 } else {
635 for (const int32_t id : componentTruthIds) ++strongComponentsPerMuon[id];
636 }
637 }
638 const bool mixed = componentTruthIds.size() >= 2;
639 if (mixed) {
640 m_groupPurity.mixedComponentSegments += retained.size();
641 const std::uint64_t packed =
642 (static_cast<std::uint64_t>(componentTruthIds.size()) << 32) | retained.size();
643 std::uint64_t current = m_groupPurity.worstMixedPacked.load(std::memory_order_relaxed);
644 while (packed > current && !m_groupPurity.worstMixedPacked.compare_exchange_weak(
645 current, packed, std::memory_order_relaxed)) {}
646 }
647 for (const int32_t id : componentTruthIds) {
648 muonComponents[id].insert(componentId);
649 if (mixed) muonEverMixed[id] = true;
650 }
651 }
652 }
653
654 if (truthDiag) {
655 std::size_t truthSegs = 0, bkgSegs = 0;
656 for (std::size_t node = 0; node < graph.nNodes; ++node) {
657 if (!keptNode[node]) continue;
658 nodeTruthId[node] >= 0 ? ++truthSegs : ++bkgSegs;
659 }
660 m_sumRetainedTruthSegments += truthSegs;
661 m_sumRetainedBkgSegments += bkgSegs;
662
663 std::vector<unsigned char> thresholded(graph.nNodes, 0);
664 for (std::size_t node = 0; node < graph.nNodes; ++node) {
665 thresholded[node] = !edgesByNode[node].empty();
666 }
667 classifyLostTruthSegments(*segments, graph, pairProbability, thresholded,
668 keptNode, nodeDropReason);
669 classifyGroupPurity(muonComponents, muonEverMixed, truthIdToAbsEta);
670 for (const int32_t id : weakCleanMuons) {
671 ++(strongComponentsPerMuon[id] > 0 ? m_groupPurity.weakCleanWithOther : m_groupPurity.weakCleanAlone);
672 }
673 }
674
675 if (!m_filteredSegmentKey.empty()) {
676 auto connectedSegments =
677 std::make_unique<ConstDataVector<xAOD::MuonSegmentContainer>>(
679 connectedSegments->reserve(graph.nNodes);
680 for (std::size_t node = 0; node < graph.nNodes; ++node) {
681 if (!keptNode[node] || !graph.segments[node]) continue;
682 connectedSegments->push_back(graph.segments[node]);
683 }
684
685 const std::size_t nConnectedSegments = connectedSegments->size();
686 SG::WriteHandle<ConstDataVector<xAOD::MuonSegmentContainer>> connectedHandle{
688 ATH_CHECK(connectedHandle.record(std::move(connectedSegments)));
689 ATH_MSG_DEBUG("Event " << ctx.eventID().event_number()
690 << ": wrote " << nConnectedSegments
691 << " ML-connected segment(s) to '"
692 << m_filteredSegmentKey.key() << "'");
693 }
694
695 ATH_MSG_DEBUG("Event " << ctx.eventID().event_number()
696 << ": ML components graphNodes=" << graph.nNodes
697 << ", thresholdedNodes=" << thresholdedNodes
698 << ", thresholdPairs=" << thresholdPairs
699 << ", mutualTopKPairs=" << mutualTopKPairs
700 << ", oneSidedTopKPairs=" << oneSidedTopKPairs
701 << ", selectedPairs=" << selectedPairKeys.size()
702 << ", components=" << componentsKept
703 << ", topologyNodes=" << topologyNodes
704 << ", retainedNodes=" << retainedNodes
705 << ", chamberSuppressedNodes=" << chamberSuppressedNodes
706 << ", nodesRejectedByMinComponent=" << nodesRejectedByMinComponent
707 << ", keepBestSegmentPerChamber=" << m_keepBestSegmentPerChamber.value()
708 << ", seedAnchors=" << anchors
709 << ", rejectedComponents=" << rejectedComponents
710 << ", threshold=" << m_pairGateThreshold.value()
711 << ", maxEdgesPerNode=" << m_maxEdgesPerNode.value()
712 << ", orphanRecoveryPairs=" << orphanRecoveryPairs
713 << ", mutualTopK=" << m_requireMutualTopKEdges.value()
714 << ", degreeCapped=" << m_useDegreeCappedComponents.value());
715
716 // Job-summed diagnostics
717 if (msgLvl(MSG::DEBUG)) {
718 m_sumGraphNodes += graph.nNodes;
719 m_sumThresholdedNodes += thresholdedNodes;
720 m_sumMutualTopKPairs += mutualTopKPairs;
721 m_sumOneSidedTopKPairs += oneSidedTopKPairs;
722 m_sumOrphanRecoveryPairs += orphanRecoveryPairs;
723 m_sumTopologyNodes += topologyNodes;
724 m_sumRetainedNodes += retainedNodes;
725 m_sumChamberSuppressedNodes += chamberSuppressedNodes;
726 m_sumNodesRejectedByMinComponent += nodesRejectedByMinComponent;
727 m_sumRejectedComponents += rejectedComponents;
728 m_sumComponentsKept += componentsKept;
729 }
730 return StatusCode::SUCCESS;
731}
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_DEBUG(x,...)
std::vector< std::vector< float > > scores
bool passed(DecisionID id, const DecisionIDContainer &)
checks if required decision ID is in the set of IDs in the container
size_t size() const
Number of registered mappings.
static const Attributes_t empty
bool msgLvl(const MSG::Level lvl) const
std::atomic< std::size_t > m_sumOnnxBkgSegments
std::atomic< std::size_t > m_sumChamberSuppressedNodes
Gaudi::Property< bool > m_keepBestSegmentPerChamber
void classifyGroupPurity(const std::unordered_map< std::int32_t, std::unordered_set< unsigned > > &muonComponents, const std::unordered_map< std::int32_t, bool > &muonEverMixed, const std::unordered_map< std::int32_t, double > &truthIdToAbsEta) const
MC-only: given the per-muon component memberships and mixed-status gathered while the per-component c...
std::atomic< std::size_t > m_sumRetainedNodes
Gaudi::Property< bool > m_anchorInnermostLayer
SG::WriteDecorHandleKey< xAOD::MuonSegmentContainer > m_pairGateDecorKey
Per-segment payload consumed by MlMsTrackSeeder: [componentId, anchorRank + 1] Empty means that the s...
Gaudi::Property< bool > m_useDegreeCappedComponents
std::atomic< std::size_t > m_sumOrphanRecoveryPairs
ToolHandle< ISegmentEdgeClassifierTool > m_edgeClassifier
Gaudi::Property< unsigned int > m_maxEdgesPerNode
std::atomic< std::size_t > m_sumThresholdedNodes
std::atomic< std::size_t > m_sumRetainedBkgSegments
Gaudi::Property< bool > m_requireMutualTopKEdges
std::atomic< std::size_t > m_sumOneSidedTopKPairs
std::atomic< std::size_t > m_sumInputBkgSegments
void classifyLostTruthSegments(const xAOD::MuonSegmentContainer &segments, const SegmentEdgeGraph &graph, const std::unordered_map< std::uint64_t, float > &pairProbability, const std::vector< unsigned char > &thresholded, const std::vector< std::uint8_t > &keptNode, const std::vector< unsigned char > &nodeDropReason) const
MC-only: classify every truth-labeled input segment that did not make it into the filtered container ...
std::atomic< std::size_t > m_sumTrueEdgesPassed
bool m_truthDiagEnabled
Whether truth-particle matching is available/enabled this job.
std::atomic< std::size_t > m_sumBkgEdgesPassed
std::atomic< std::size_t > m_sumBkgEdgesTotal
std::atomic< std::size_t > m_sumGraphNodes
Job-summed post-ONNX selection counters (see execute()).
Gaudi::Property< unsigned int > m_minSegmentsPerComponent
std::atomic< std::size_t > m_sumOnnxTruthSegments
std::atomic< std::size_t > m_sumInputTruthSegments
std::atomic< std::size_t > m_sumNodesRejectedByMinComponent
std::atomic< std::size_t > m_sumTrueEdgesTotal
std::atomic< std::size_t > m_sumRetainedTruthSegments
SG::WriteHandleKey< ConstDataVector< xAOD::MuonSegmentContainer > > m_filteredSegmentKey
Gaudi::Property< bool > m_recoverOrphanNodes
std::atomic< std::size_t > m_sumMutualTopKPairs
Gaudi::Property< unsigned int > m_maxEdgesPerNodePerLayer
SG::WriteDecorHandleKey< xAOD::MuonSegmentContainer > m_componentScoreDecorKey
Per-segment ML scores for MlMsTrackSeeder: [strongest selected cross-layer edge of the segment's comp...
std::atomic< std::size_t > m_sumRejectedComponents
SG::ReadHandleKey< xAOD::MuonSegmentContainer > m_segmentKey
Gaudi::Property< unsigned int > m_seedAnchorsPerComponent
Gaudi::Property< float > m_pairGateThreshold
std::atomic< std::size_t > m_sumTopologyNodes
std::atomic< std::size_t > m_sumComponentsKept
StatusCode record(std::unique_ptr< T > data)
Record a const object to the store.
virtual double eta() const override final
The pseudorapidity ( ) of the particle.
int count(std::string s, const std::string &regx)
count how many occurances of a regx are in a string
Definition hcg.cxx:148
bool first
Definition DeMoScan.py:534
H5::CompType packed(H5::CompType in)
MuonR4::SegmentQualitySorter SegmentQualityOrder
Common quality ordering for segment representatives, shared with MlMsTrackSeeder so that graph constr...
int compareFloatDescending(float first, float second)
Three-way descending comparison which also orders NaN last.
bool isBarrel(const ChIndex index)
Returns true if the chamber index points to a barrel chamber.
@ VIEW_ELEMENTS
this data object is a view, it does not own its elmts
const T * get(const ReadCondHandleKey< T > &key, const EventContext &ctx)
Convenience function to retrieve an object given a ReadCondHandleKey.
@ layer
Definition HitInfo.h:79
@ u
Enums for curvilinear frames.
Definition ParamDefs.h:77
order
Configure Herwig7.
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.
MuonSegmentContainer_v1 MuonSegmentContainer
Definition of the current "MuonSegment container version".
static constexpr std::size_t kCrossScoreBins
Components spanning >= 2 layers (the only ones that can seed), per outcome:

◆ extraDeps_update_handler()

void AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::extraDeps_update_handler ( Gaudi::Details::PropertyBase & ExtraDeps)
protectedinherited

Add StoreName to extra input/output deps as needed.

use the logic of the VarHandleKey to parse the DataObjID keys supplied via the ExtraInputs and ExtraOuputs Properties to add the StoreName if it's not explicitly given

◆ extraOutputDeps()

const DataObjIDColl & AthCommonAlgorithm< Gaudi::Algorithm >::extraOutputDeps ( ) const
overridevirtualinherited

Return the list of extra output dependencies.

This list is extended to include symlinks implied by inheritance relations.

Definition at line 89 of file AthCommonAlgorithm.cxx.

54{
55 // If we didn't find any symlinks to add, just return the collection
56 // from the base class. Otherwise, return the extended collection.
57 if (!m_extendedExtraObjects.empty()) {
59 }
61}
Common base class for algorithms.

◆ filterPassed()

virtual bool AthCommonAlgorithm< Gaudi::Algorithm >::filterPassed ( const EventContext & ctx) const
inlinevirtualinherited

Get filter decision:

Definition at line 93 of file AthCommonAlgorithm.h.

93 {
94 return execState( ctx ).filterPassed();
95 }
virtual bool filterPassed(const EventContext &ctx) const
Get filter decision:

◆ finalize()

StatusCode MuonML::SegmentEdgeInferenceAlg::finalize ( )
override

Log of the post-ONNX rank/mutuality and min-component-size selection.

Definition at line 935 of file SegmentEdgeInferenceAlg.cxx.

935 {
936 // The job-summed counters are only filled, and the summaries only built, at DEBUG level.
937 if (!msgLvl(MSG::DEBUG)) return StatusCode::SUCCESS;
939 "SegmentEdgeInferenceAlg post-ONNX selection summary (job-summed): "
940 << "graphNodes=" << m_sumGraphNodes
941 << ", thresholdedNodes=" << m_sumThresholdedNodes
942 << " (>=PairGateThreshold=" << m_pairGateThreshold.value() << ")"
943 << ", mutualTopKPairs=" << m_sumMutualTopKPairs
944 << ", oneSidedTopKPairs(rejected by RequireMutualTopKEdges="
946 << " (MaxEdgesPerNode=" << m_maxEdgesPerNode.value()
947 << ", MaxEdgesPerNodePerLayer=" << m_maxEdgesPerNodePerLayer.value() << " (0 = global top-K)"
948 << "; a thresholded edge is dropped here purely by rank, "
949 "independent of PairGateThreshold)"
950 << ", orphanRecoveryPairs=" << m_sumOrphanRecoveryPairs
951 << ", chamberSuppressedNodes=" << m_sumChamberSuppressedNodes
952 << " (KeepBestSegmentPerChamber=" << m_keepBestSegmentPerChamber.value() << ")"
953 << ", nodesRejectedByMinComponent=" << m_sumNodesRejectedByMinComponent
954 << " (MinSegmentsPerComponent=" << m_minSegmentsPerComponent.value() << ")"
955 << ", rejectedComponents=" << m_sumRejectedComponents
956 << ", componentsKept=" << m_sumComponentsKept
957 << ", topologyNodes=" << m_sumTopologyNodes
958 << ", retainedNodes=" << m_sumRetainedNodes);
959
960 if (m_truthDiagEnabled) {
961 ATH_MSG_DEBUG("True cross-layer edges above PairGateThreshold kept by the selection, selected/total: barrel="
962 << m_sumTrueCrossLayerSelected[0] << "/" << m_sumTrueCrossLayerEdges[0]
963 << ", endcap=" << m_sumTrueCrossLayerSelected[1] << "/" << m_sumTrueCrossLayerEdges[1]);
965 "SegmentEdgeInferenceAlg truth-vs-background funnel (job-summed; "
966 "truth segment = getTruthMatchedParticle(seg) != nullptr, matching "
967 "SegmentDumperAlg::m_segmentHasTruth, no isMuon() filter, no G4 "
968 "pseudo-label fallback): "
969 << "input: truth=" << m_sumInputTruthSegments
970 << " bkg=" << m_sumInputBkgSegments
971 << "; reachesONNX: truth=" << m_sumOnnxTruthSegments
972 << " bkg=" << m_sumOnnxBkgSegments
973 << "; retained: truth=" << m_sumRetainedTruthSegments
974 << " bkg=" << m_sumRetainedBkgSegments
975 << "; edge-level (same truth-particle index on both endpoints) at "
976 "PairGateThreshold=" << m_pairGateThreshold.value()
977 << ": trueEdges passed/total=" << m_sumTrueEdgesPassed << "/"
979 << ", bkgEdges passed/total=" << m_sumBkgEdgesPassed << "/"
981
982 constexpr std::array<const char*, TruthLossCounters::kCategories> names{
983 "A1 no possible partner: singleton muon segment",
984 "A2 no possible partner: same-chamber partners only",
985 "B0 never reached ONNX: segment dropped by MaxSegmentsPerBucket",
986 "B1 never reached ONNX: partners dropped by MaxSegmentsPerBucket",
987 "B2 never reached ONNX: outside sector window (MaxDeltaSector)",
988 "B3 never reached ONNX: outside angle window (MaxDeltaThetaDeg)",
989 "B4 never reached ONNX: pre-ONNX edge caps",
990 "C true pair(s) scored below PairGateThreshold",
991 "D1 passed threshold, removed: KeepBestSegmentPerChamber",
992 "D2 passed threshold, removed: component rejected (MinSegmentsPerComponent/anchors)",
993 "D3 passed threshold, removed: not selected by top-K/mutual gate"};
994 constexpr std::size_t kColumns = TruthLossCounters::kColumns;
995 ATH_MSG_DEBUG("Lost truth segments by stage (job-summed; truth segment = "
996 "input segment with a truth particle, lost = not in the filtered "
997 "container). Columns: all | barrel | transition | endcap | "
998 "belonging to a muon that lost seedability");
999 std::size_t lostTotal = 0;
1000 for (std::size_t c = 0; c < names.size(); ++c) {
1001 const auto value = [&](std::size_t col) { return m_truthLoss.category[c * kColumns + col].load(); };
1002 lostTotal += value(0);
1003 ATH_MSG_DEBUG(" " << names[c] << ": " << value(0) << " | " << value(1)
1004 << " | " << value(2) << " | " << value(3) << " | " << value(4));
1005 }
1006 ATH_MSG_DEBUG(" check: sum of categories=" << lostTotal
1007 << ", input truth - retained truth="
1009 ATH_MSG_DEBUG(" truth muons with >=2 input chambers (all|barrel|transition|endcap): "
1010 << m_truthLoss.muonsSeedable[0].load() << "|" << m_truthLoss.muonsSeedable[1].load()
1011 << "|" << m_truthLoss.muonsSeedable[2].load() << "|" << m_truthLoss.muonsSeedable[3].load()
1012 << "; of which <2 chambers retained: "
1013 << m_truthLoss.muonsSeedLost[0].load() << "|" << m_truthLoss.muonsSeedLost[1].load()
1014 << "|" << m_truthLoss.muonsSeedLost[2].load() << "|" << m_truthLoss.muonsSeedLost[3].load());
1015 ATH_MSG_DEBUG(" category C descriptors: best true-pair score <1e-3/1e-3..1e-2/1e-2..2e-2/2e-2..5e-2/>=5e-2: "
1016 << m_truthLoss.scoreBin[0].load() << "/" << m_truthLoss.scoreBin[1].load() << "/"
1017 << m_truthLoss.scoreBin[2].load() << "/" << m_truthLoss.scoreBin[3].load() << "/"
1018 << m_truthLoss.scoreBin[4].load()
1019 << "; pair sector delta same/adjacent: " << m_truthLoss.sectorDelta[0].load() << "/"
1020 << m_truthLoss.sectorDelta[1].load()
1021 << "; layer pair Inner-Middle/Middle-Outer/Inner-Outer/other: "
1022 << m_truthLoss.layerPair[0].load() << "/" << m_truthLoss.layerPair[1].load() << "/"
1023 << m_truthLoss.layerPair[2].load() << "/" << m_truthLoss.layerPair[3].load()
1024 << "; muon input segments 2/3/4/5+: " << m_truthLoss.muonSegments[0].load() << "/"
1025 << m_truthLoss.muonSegments[1].load() << "/" << m_truthLoss.muonSegments[2].load() << "/"
1026 << m_truthLoss.muonSegments[3].load()
1027 << "; nPrecisionHits <=4/5-6/>=7: " << m_truthLoss.precisionHits[0].load() << "/"
1028 << m_truthLoss.precisionHits[1].load() << "/" << m_truthLoss.precisionHits[2].load());
1029
1031 "SegmentEdgeInferenceAlg grouping purity (job-summed; components cover "
1032 "only kept components, i.e. those that survived MinSegmentsPerComponent "
1033 "and appear in the output): "
1034 << "clean=" << m_groupPurity.componentsByOutcome[0].load()
1035 << " mixed=" << m_groupPurity.componentsByOutcome[1].load()
1036 << " bkgOnly=" << m_groupPurity.componentsByOutcome[2].load()
1037 << "; segments inside mixed components=" << m_groupPurity.mixedComponentSegments.load()
1038 << "; component size histogram (2/3/4/5-9/10-19/20+): "
1039 << m_groupPurity.componentSizeHist[0].load() << "/" << m_groupPurity.componentSizeHist[1].load() << "/"
1040 << m_groupPurity.componentSizeHist[2].load() << "/" << m_groupPurity.componentSizeHist[3].load() << "/"
1041 << m_groupPurity.componentSizeHist[4].load() << "/" << m_groupPurity.componentSizeHist[5].load());
1042
1043 // Kept components per outcome, binned by a candidate component-level cut variable.
1044 const auto printByOutcome = [&](const char* label, const auto& counters, std::size_t nBins) {
1045 constexpr std::array<const char*, 3> outcomes{"clean", "mixed", "bkgOnly"};
1046 std::stringstream sstr{};
1047 for (std::size_t outcome = 0; outcome < outcomes.size(); ++outcome) {
1048 sstr << " " << outcomes[outcome] << "=";
1049 for (std::size_t bin = 0; bin < nBins; ++bin) {
1050 sstr << (bin ? "/" : "") << counters[outcome * nBins + bin].load();
1051 }
1052 }
1053 ATH_MSG_DEBUG(" kept components by " << label << ":" << sstr.str());
1054 };
1055 printByOutcome("strongest selected-edge score (<0.05/0.05-0.1/0.1-0.3/0.3-0.5/>=0.5)",
1056 m_groupPurity.scoreByOutcome, GroupPurityCounters::kScoreBins);
1057 printByOutcome("retained segments (2/3/4/5-9/10-19/20+)",
1058 m_groupPurity.sizeByOutcome, GroupPurityCounters::kSizeBins);
1059 printByOutcome("distinct layers (1/2/3/4+)",
1060 m_groupPurity.layersByOutcome, GroupPurityCounters::kLayerBins);
1061 printByOutcome("strongest selected cross-layer edge, components with >=2 layers "
1062 "(<0.05/0.05-0.1/0.1-0.3/0.3-0.5/0.5-0.8/>=0.8)",
1063 m_groupPurity.crossScoreByOutcome, GroupPurityCounters::kCrossScoreBins);
1064 printByOutcome("number of selected cross-layer edges, components with >=2 layers (0/1/2/3+)",
1065 m_groupPurity.crossEdgesByOutcome, GroupPurityCounters::kCrossEdgeBins);
1066 ATH_MSG_DEBUG(" D8 clean components with >=2 layers and strongest cross-layer edge < 0.05: "
1067 << "their muon has another component with >=2 layers and a stronger edge="
1068 << m_groupPurity.weakCleanWithOther.load()
1069 << ", it is the muon's only such component=" << m_groupPurity.weakCleanAlone.load());
1070
1071 const std::uint64_t worstMixed = m_groupPurity.worstMixedPacked.load();
1072 ATH_MSG_DEBUG(" worst mixed component this job: "
1073 << (worstMixed >> 32) << " distinct truth muons in "
1074 << (worstMixed & 0xffffffffull) << " segments"
1075 << (worstMixed == 0 ? " (no mixed components found)" : ""));
1076
1078 " truth-muon grouping outcome (all|barrel|transition|endcap; muons with "
1079 ">=1 retained segment only, fully-lost muons excluded -- see muonsSeedLost above): "
1080 << "clean=" << m_groupPurity.muonOutcome[0 * 3 + 0].load() << "|"
1081 << m_groupPurity.muonOutcome[1 * 3 + 0].load() << "|"
1082 << m_groupPurity.muonOutcome[2 * 3 + 0].load() << "|"
1083 << m_groupPurity.muonOutcome[3 * 3 + 0].load()
1084 << "; mixed=" << m_groupPurity.muonOutcome[0 * 3 + 1].load() << "|"
1085 << m_groupPurity.muonOutcome[1 * 3 + 1].load() << "|"
1086 << m_groupPurity.muonOutcome[2 * 3 + 1].load() << "|"
1087 << m_groupPurity.muonOutcome[3 * 3 + 1].load()
1088 << "; fragmented=" << m_groupPurity.muonOutcome[0 * 3 + 2].load() << "|"
1089 << m_groupPurity.muonOutcome[1 * 3 + 2].load() << "|"
1090 << m_groupPurity.muonOutcome[2 * 3 + 2].load() << "|"
1091 << m_groupPurity.muonOutcome[3 * 3 + 2].load());
1092 }
1093 return StatusCode::SUCCESS;
1094}
std::string label(const std::string &format, int i)
Definition label.h:19
load(f, use_proxy=1, key=None)

◆ initialize()

StatusCode MuonML::SegmentEdgeInferenceAlg::initialize ( )
override

Definition at line 118 of file SegmentEdgeInferenceAlg.cxx.

118 {
119 ATH_CHECK(m_segmentKey.initialize());
120 ATH_CHECK(m_pairGateDecorKey.initialize());
123 ATH_CHECK(m_edgeClassifier.retrieve());
124 m_truthDiagEnabled = m_edgeClassifier->enableTruthDiagnostics();
125 return StatusCode::SUCCESS;
126}

◆ inputHandles()

virtual std::vector< Gaudi::DataHandle * > AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::inputHandles ( ) const
overridevirtualinherited

Return this algorithm's input handles.

We override this to include handle instances from key arrays if they have not yet been declared. See comments on updateVHKA.

◆ isClonable()

virtual bool AthCommonAlgorithm< Gaudi::Algorithm >::isClonable ( ) const
inlineoverridevirtualinherited

Specify if the algorithm is clonable.

Only relevant for non-reentrant algorithms. Actual number of clones needs to be set via the "Cardinality" property.

Reimplemented in AFP_DigiTop, AlgB, AlgT, BCM_Digitization, CscDigitBuilder, CscDigitToCscRDO, G4AtlasAlg, G4RunAlg, HGTD_Digitization, HiveAlgBase, InDet::GNNSeedingTrackMaker, InDet::SCT_Clusterization, InDet::SiSPGNNTrackMaker, InDet::SiSPSeededTrackFinder, InDet::SiTrackerSpacePointFinder, ISF::SimKernelMT, ITk::StripDigitization, ITkPixelCablingAlg, ITkStripCablingAlg, LArHitEMapMaker, LArTTL1Maker, LUCID_DigiTop, LVL1::L1TopoSimulation, MergeCalibHits, MergeGenericMuonSimHitColl, MergeHijingPars, MergeMcEventCollection, MergeTrackRecordCollection, MergeTruthJets, MergeTruthParticles, MuonDigitizer, PileUpMTAlg, PixelDigitization, RoIBResultToxAOD, SCT_ByteStreamErrorsTestAlg, SCT_CablingCondAlgFromCoraCool, SCT_CablingCondAlgFromText, SCT_ConditionsParameterTestAlg, SCT_ConditionsSummaryTestAlg, SCT_ConfigurationConditionsTestAlg, SCT_Digitization, SCT_FlaggedConditionTestAlg, SCT_LinkMaskingTestAlg, SCT_MajorityConditionsTestAlg, SCT_ModuleVetoTestAlg, SCT_MonitorConditionsTestAlg, SCT_PrepDataToxAOD, SCT_RawDataToxAOD, SCT_ReadCalibChipDataTestAlg, SCT_ReadCalibDataTestAlg, SCT_RODVetoTestAlg, SCT_SensorsTestAlg, SCT_SiliconConditionsTestAlg, SCT_StripVetoTestAlg, SCT_TdaqEnabledTestAlg, SCT_TestCablingAlg, SCTEventFlagWriter, SCTRawDataProvider, SCTSiLorentzAngleTestAlg, SCTSiPropertiesTestAlg, SGInputLoader, Simulation::BeamEffectsAlg, TileHitVecToCnt, TileMuonFitter, TilePulseForTileMuonReceiver, TileRawChannelMaker, TRTDigitization, and ZDC_DigiTop.

Definition at line 68 of file AthCommonAlgorithm.h.

68 {
69 return true;
70 }

◆ msg()

MsgStream & AthCommonMsg< Gaudi::Algorithm >::msg ( ) const
inlineinherited

Definition at line 24 of file AthCommonMsg.h.

24 {
25 return this->msgStream();
26 }

◆ msgLvl()

bool AthCommonMsg< Gaudi::Algorithm >::msgLvl ( const MSG::Level lvl) const
inlineinherited

Definition at line 30 of file AthCommonMsg.h.

30 {
31 return this->msgLevel(lvl);
32 }

◆ outputHandles()

virtual std::vector< Gaudi::DataHandle * > AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::outputHandles ( ) const
overridevirtualinherited

Return this algorithm's output handles.

We override this to include handle instances from key arrays if they have not yet been declared. See comments on updateVHKA.

◆ renounce()

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 > AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::renounce ( T & h)
inlineprotectedinherited

Definition at line 368 of file AthCommonDataStore.h.

369 {
370 h.renounce();
372 }
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)

◆ renounceArray()

void AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::renounceArray ( SG::VarHandleKeyArray & handlesArray)
inlineprotectedinherited

remove all handles from I/O resolution

Definition at line 352 of file AthCommonDataStore.h.

352 {
354 }

◆ setFilterPassed()

virtual void AthCommonAlgorithm< Gaudi::Algorithm >::setFilterPassed ( bool state,
const EventContext & ctx ) const
inlinevirtualinherited

Set filter decision:

Definition at line 99 of file AthCommonAlgorithm.h.

99 {
101 }
virtual void setFilterPassed(bool state, const EventContext &ctx) const
Set filter decision:

◆ sysExecute()

StatusCode AthCommonAlgorithm< Gaudi::Algorithm >::sysExecute ( const EventContext & ctx)
overridevirtualinherited

Execute an algorithm.

We override this in order to work around an issue with the Algorithm base class storing the event context in a member variable that can cause crashes in MT jobs.

Reimplemented in AthAnalysisAlgorithm.

Definition at line 80 of file AthCommonAlgorithm.cxx.

41{
42 return BaseAlg::sysExecute (ctx);
43}

◆ sysInitialize()

StatusCode AthCommonAlgorithm< Gaudi::Algorithm >::sysInitialize ( )
overridevirtualinherited

Override sysInitialize.

Override sysInitialize from the base class.

Loop through all output handles, and if they're WriteCondHandles, automatically register them and this Algorithm with the CondSvc

Scan through all outputHandles, and if they're WriteCondHandles, register them with the CondSvc

Reimplemented from AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >.

Reimplemented in AthAnalysisAlgorithm, AthHistogramAlgorithm, HypoBase, InputMakerBase, and PyAthena::Alg.

Definition at line 60 of file AthCommonAlgorithm.cxx.

71 {
73
74 if (sc.isFailure()) {
75 return sc;
76 }
77
78 ServiceHandle<ICondSvc> cs("CondSvc",name());
79 for (auto h : outputHandles()) {
80 if (h->isCondition() && h->mode() == Gaudi::DataHandle::Writer) {
81 // do this inside the loop so we don't create the CondSvc until needed
82 if ( cs.retrieve().isFailure() ) {
83 ATH_MSG_WARNING("no CondSvc found: won't autoreg WriteCondHandles");
85 }
86 if (cs->regHandle(this,*h).isFailure()) {
88 ATH_MSG_ERROR("unable to register WriteCondHandle " << h->fullKey()
89 << " with CondSvc");
90 }
91 }
92 }
93 return sc;
94}
#define ATH_MSG_ERROR(x,...)
virtual StatusCode sysInitialize() override
virtual std::vector< Gaudi::DataHandle * > outputHandles() const override

◆ sysStart()

virtual StatusCode AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::sysStart ( )
overridevirtualinherited

Handle START transition.

We override this in order to make sure that conditions handle keys can cache a pointer to the conditions container.

◆ updateVHKA()

void AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::updateVHKA ( Gaudi::Details::PropertyBase & )
inlineinherited

Definition at line 298 of file AthCommonDataStore.h.

298 {
299 for (auto &a : m_vhka) {
301 for (auto k : keys) {
302 k->setOwner(this);
303 }
304 }
305 }

Member Data Documentation

◆ ATLAS_THREAD_SAFE [1/4]

GroupPurityCounters m_groupPurity MuonML::SegmentEdgeInferenceAlg::ATLAS_THREAD_SAFE
mutableprivate

Every element is a std::atomic, updated only with atomic additions/CAS.

Definition at line 184 of file SegmentEdgeInferenceAlg.h.

◆ ATLAS_THREAD_SAFE [2/4]

TruthLossCounters m_truthLoss MuonML::SegmentEdgeInferenceAlg::ATLAS_THREAD_SAFE
mutableprivate

Every element is a std::atomic, updated only with atomic additions.

Definition at line 154 of file SegmentEdgeInferenceAlg.h.

◆ ATLAS_THREAD_SAFE [3/4]

std::array<std::atomic<std::size_t>, 2> m_sumTrueCrossLayerSelected MuonML::SegmentEdgeInferenceAlg::ATLAS_THREAD_SAFE {}
mutableprivate

Definition at line 134 of file SegmentEdgeInferenceAlg.h.

134{};

◆ ATLAS_THREAD_SAFE [4/4]

std::array<std::atomic<std::size_t>, 2> m_sumTrueCrossLayerEdges MuonML::SegmentEdgeInferenceAlg::ATLAS_THREAD_SAFE {}
mutableprivate

True cross-layer edges above PairGateThreshold, total and kept by the selection; barrel / endcap Every element is a std::atomic, updated only with atomic increments.

Definition at line 133 of file SegmentEdgeInferenceAlg.h.

133{};

◆ m_anchorInnermostLayer

Gaudi::Property<bool> MuonML::SegmentEdgeInferenceAlg::m_anchorInnermostLayer
private
Initial value:
{
this, "AnchorInnermostLayer", false,
"Restrict seed anchors to inner segment(s); with SeedAnchorsPerComponent > 0 keep only "
"the inner segment(s) with the strongest selected edge towards another layer"}

Definition at line 89 of file SegmentEdgeInferenceAlg.h.

89 {
90 this, "AnchorInnermostLayer", false,
91 "Restrict seed anchors to inner segment(s); with SeedAnchorsPerComponent > 0 keep only "
92 "the inner segment(s) with the strongest selected edge towards another layer"};

◆ m_componentScoreDecorKey

SG::WriteDecorHandleKey<xAOD::MuonSegmentContainer> MuonML::SegmentEdgeInferenceAlg::m_componentScoreDecorKey
private
Initial value:
{
this, "ComponentScoreDecoration", "MsSegments.mlComponentScore"}

Per-segment ML scores for MlMsTrackSeeder: [strongest selected cross-layer edge of the segment's component, strongest selected cross-layer edge of the segment itself] (-1 if none).

Written for the segments of kept components.

Definition at line 60 of file SegmentEdgeInferenceAlg.h.

60 {
61 this, "ComponentScoreDecoration", "MsSegments.mlComponentScore"};

◆ m_detStore

StoreGateSvc_t AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::m_detStore
privateinherited

Pointer to StoreGate (detector store by default).

Definition at line 381 of file AthCommonDataStore.h.

◆ m_edgeClassifier

ToolHandle<ISegmentEdgeClassifierTool> MuonML::SegmentEdgeInferenceAlg::m_edgeClassifier {this, "EdgeClassifierTool", "MuonML::SegmentEdgeClassifierTool/SegmentEdgeClassifierTool"}
private

Definition at line 99 of file SegmentEdgeInferenceAlg.h.

99{this, "EdgeClassifierTool", "MuonML::SegmentEdgeClassifierTool/SegmentEdgeClassifierTool"};

◆ m_evtStore

StoreGateSvc_t AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::m_evtStore
privateinherited

Pointer to StoreGate (event store by default).

Definition at line 378 of file AthCommonDataStore.h.

◆ m_extendedExtraObjects

DataObjIDColl AthCommonAlgorithm< Gaudi::Algorithm >::m_extendedExtraObjects
privateinherited

Extra output dependency collection, extended by AthAlgorithmDHUpdate to add symlinks.

Empty if no symlinks were found.

Definition at line 108 of file AthCommonAlgorithm.h.

◆ m_filteredSegmentKey

SG::WriteHandleKey<ConstDataVector<xAOD::MuonSegmentContainer> > MuonML::SegmentEdgeInferenceAlg::m_filteredSegmentKey
private
Initial value:
{
this, "FilteredSegmentKey", "",
"Optional VIEW container containing only segments incident to a "
"post-classifier selected edge; empty disables the output"}

Definition at line 62 of file SegmentEdgeInferenceAlg.h.

62 {
63 this, "FilteredSegmentKey", "",
64 "Optional VIEW container containing only segments incident to a "
65 "post-classifier selected edge; empty disables the output"};

◆ m_keepBestSegmentPerChamber

Gaudi::Property<bool> MuonML::SegmentEdgeInferenceAlg::m_keepBestSegmentPerChamber
private
Initial value:
{
this, "KeepBestSegmentPerChamber", true,
"Keep only the ML-best segment in each chamber within a component"}

Definition at line 93 of file SegmentEdgeInferenceAlg.h.

93 {
94 this, "KeepBestSegmentPerChamber", true,
95 "Keep only the ML-best segment in each chamber within a component"};

◆ m_maxEdgesPerNode

Gaudi::Property<unsigned int> MuonML::SegmentEdgeInferenceAlg::m_maxEdgesPerNode
private
Initial value:
{
this, "MaxEdgesPerNode", 2,
"Keep at most this many highest-score neighbours per segment when forming the ML path graph; zero keeps all"}

Definition at line 69 of file SegmentEdgeInferenceAlg.h.

69 {
70 this, "MaxEdgesPerNode", 2,
71 "Keep at most this many highest-score neighbours per segment when forming the ML path graph; zero keeps all"};

◆ m_maxEdgesPerNodePerLayer

Gaudi::Property<unsigned int> MuonML::SegmentEdgeInferenceAlg::m_maxEdgesPerNodePerLayer
private
Initial value:
{
this, "MaxEdgesPerNodePerLayer", 0,
"If > 0, each segment nominates its this-many highest-score edges towards EACH layer (Inner, "
"Middle, Outer, Extended, ...) instead of its MaxEdgesPerNode best edges overall; zero keeps "
"the global top-K"}

Definition at line 72 of file SegmentEdgeInferenceAlg.h.

72 {
73 this, "MaxEdgesPerNodePerLayer", 0,
74 "If > 0, each segment nominates its this-many highest-score edges towards EACH layer (Inner, "
75 "Middle, Outer, Extended, ...) instead of its MaxEdgesPerNode best edges overall; zero keeps "
76 "the global top-K"};

◆ m_minSegmentsPerComponent

Gaudi::Property<unsigned int> MuonML::SegmentEdgeInferenceAlg::m_minSegmentsPerComponent
private
Initial value:
{
this, "MinSegmentsPerComponent", 2,
"Discard ML components with fewer retained segments"}

Definition at line 96 of file SegmentEdgeInferenceAlg.h.

96 {
97 this, "MinSegmentsPerComponent", 2,
98 "Discard ML components with fewer retained segments"};

◆ m_pairGateDecorKey

SG::WriteDecorHandleKey<xAOD::MuonSegmentContainer> MuonML::SegmentEdgeInferenceAlg::m_pairGateDecorKey
private
Initial value:
{
this, "PairGateDecoration", "MsSegments.mlTrackComponent"}

Per-segment payload consumed by MlMsTrackSeeder: [componentId, anchorRank + 1] Empty means that the segment was rejected by the ML preselection.

Definition at line 55 of file SegmentEdgeInferenceAlg.h.

55 {
56 this, "PairGateDecoration", "MsSegments.mlTrackComponent"};

◆ m_pairGateThreshold

Gaudi::Property<float> MuonML::SegmentEdgeInferenceAlg::m_pairGateThreshold
private
Initial value:
{
this, "PairGateThreshold", 0.5f,
"Minimum edge probability used to form ML track components"}

Definition at line 66 of file SegmentEdgeInferenceAlg.h.

66 {
67 this, "PairGateThreshold", 0.5f,
68 "Minimum edge probability used to form ML track components"};

◆ m_recoverOrphanNodes

Gaudi::Property<bool> MuonML::SegmentEdgeInferenceAlg::m_recoverOrphanNodes
private
Initial value:
{
this, "RecoverOrphanNodes", true,
"For a thresholded node without a mutual top-K edge, retain its best one-sided top-K association"}

Definition at line 83 of file SegmentEdgeInferenceAlg.h.

83 {
84 this, "RecoverOrphanNodes", true,
85 "For a thresholded node without a mutual top-K edge, retain its best one-sided top-K association"};

◆ m_requireMutualTopKEdges

Gaudi::Property<bool> MuonML::SegmentEdgeInferenceAlg::m_requireMutualTopKEdges
private
Initial value:
{
this, "RequireMutualTopKEdges", true,
"Retain an ML association only when both endpoint segments rank it in their top MaxEdgesPerNode scores"}

Definition at line 80 of file SegmentEdgeInferenceAlg.h.

80 {
81 this, "RequireMutualTopKEdges", true,
82 "Retain an ML association only when both endpoint segments rank it in their top MaxEdgesPerNode scores"};

◆ m_seedAnchorsPerComponent

Gaudi::Property<unsigned int> MuonML::SegmentEdgeInferenceAlg::m_seedAnchorsPerComponent
private
Initial value:
{
this, "SeedAnchorsPerComponent", 0,
"Number of highest-score segments that may seed each ML component; zero keeps every retained segment"}

Definition at line 86 of file SegmentEdgeInferenceAlg.h.

86 {
87 this, "SeedAnchorsPerComponent", 0,
88 "Number of highest-score segments that may seed each ML component; zero keeps every retained segment"};

◆ m_segmentKey

SG::ReadHandleKey<xAOD::MuonSegmentContainer> MuonML::SegmentEdgeInferenceAlg::m_segmentKey {this, "SegmentKey", "MsSegments"}
private

Definition at line 52 of file SegmentEdgeInferenceAlg.h.

52{this, "SegmentKey", "MsSegments"};

◆ m_sumBkgEdgesPassed

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumBkgEdgesPassed {0}
mutableprivate

Definition at line 136 of file SegmentEdgeInferenceAlg.h.

136{0};

◆ m_sumBkgEdgesTotal

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumBkgEdgesTotal {0}
mutableprivate

Definition at line 135 of file SegmentEdgeInferenceAlg.h.

135{0};

◆ m_sumChamberSuppressedNodes

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumChamberSuppressedNodes {0}
mutableprivate

Definition at line 113 of file SegmentEdgeInferenceAlg.h.

113{0};

◆ m_sumComponentsKept

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumComponentsKept {0}
mutableprivate

Definition at line 116 of file SegmentEdgeInferenceAlg.h.

116{0};

◆ m_sumGraphNodes

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumGraphNodes {0}
mutableprivate

Job-summed post-ONNX selection counters (see execute()).

Definition at line 106 of file SegmentEdgeInferenceAlg.h.

106{0};

◆ m_sumInputBkgSegments

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumInputBkgSegments {0}
mutableprivate

Definition at line 124 of file SegmentEdgeInferenceAlg.h.

124{0};

◆ m_sumInputTruthSegments

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumInputTruthSegments {0}
mutableprivate

Definition at line 123 of file SegmentEdgeInferenceAlg.h.

123{0};

◆ m_sumMutualTopKPairs

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumMutualTopKPairs {0}
mutableprivate

Definition at line 108 of file SegmentEdgeInferenceAlg.h.

108{0};

◆ m_sumNodesRejectedByMinComponent

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumNodesRejectedByMinComponent {0}
mutableprivate

Definition at line 114 of file SegmentEdgeInferenceAlg.h.

114{0};

◆ m_sumOneSidedTopKPairs

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumOneSidedTopKPairs {0}
mutableprivate

Definition at line 109 of file SegmentEdgeInferenceAlg.h.

109{0};

◆ m_sumOnnxBkgSegments

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumOnnxBkgSegments {0}
mutableprivate

Definition at line 126 of file SegmentEdgeInferenceAlg.h.

126{0};

◆ m_sumOnnxTruthSegments

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumOnnxTruthSegments {0}
mutableprivate

Definition at line 125 of file SegmentEdgeInferenceAlg.h.

125{0};

◆ m_sumOrphanRecoveryPairs

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumOrphanRecoveryPairs {0}
mutableprivate

Definition at line 110 of file SegmentEdgeInferenceAlg.h.

110{0};

◆ m_sumRejectedComponents

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumRejectedComponents {0}
mutableprivate

Definition at line 115 of file SegmentEdgeInferenceAlg.h.

115{0};

◆ m_sumRetainedBkgSegments

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumRetainedBkgSegments {0}
mutableprivate

Definition at line 128 of file SegmentEdgeInferenceAlg.h.

128{0};

◆ m_sumRetainedNodes

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumRetainedNodes {0}
mutableprivate

Definition at line 112 of file SegmentEdgeInferenceAlg.h.

112{0};

◆ m_sumRetainedTruthSegments

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumRetainedTruthSegments {0}
mutableprivate

Definition at line 127 of file SegmentEdgeInferenceAlg.h.

127{0};

◆ m_sumThresholdedNodes

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumThresholdedNodes {0}
mutableprivate

Definition at line 107 of file SegmentEdgeInferenceAlg.h.

107{0};

◆ m_sumTopologyNodes

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumTopologyNodes {0}
mutableprivate

Definition at line 111 of file SegmentEdgeInferenceAlg.h.

111{0};

◆ m_sumTrueEdgesPassed

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumTrueEdgesPassed {0}
mutableprivate

Definition at line 130 of file SegmentEdgeInferenceAlg.h.

130{0};

◆ m_sumTrueEdgesTotal

std::atomic<std::size_t> MuonML::SegmentEdgeInferenceAlg::m_sumTrueEdgesTotal {0}
mutableprivate

Definition at line 129 of file SegmentEdgeInferenceAlg.h.

129{0};

◆ m_truthDiagEnabled

bool MuonML::SegmentEdgeInferenceAlg::m_truthDiagEnabled {false}
private

Whether truth-particle matching is available/enabled this job.

Definition at line 103 of file SegmentEdgeInferenceAlg.h.

103{false};

◆ m_useDegreeCappedComponents

Gaudi::Property<bool> MuonML::SegmentEdgeInferenceAlg::m_useDegreeCappedComponents
private
Initial value:
{
this, "UseDegreeCappedComponents", false,
"Use a global greedy degree cap instead of the mutual top-K path graph"}

Definition at line 77 of file SegmentEdgeInferenceAlg.h.

77 {
78 this, "UseDegreeCappedComponents", false,
79 "Use a global greedy degree cap instead of the mutual top-K path graph"};

◆ m_varHandleArraysDeclared

bool AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::m_varHandleArraysDeclared
privateinherited

Definition at line 387 of file AthCommonDataStore.h.

◆ m_vhka

std::vector<SG::VarHandleKeyArray*> AthCommonDataStore< AthCommonMsg< Gaudi::Algorithm > >::m_vhka
privateinherited

Definition at line 386 of file AthCommonDataStore.h.

◆ m_warnedMissingToolDiagnostics

std::atomic<bool> MuonML::SegmentEdgeInferenceAlg::m_warnedMissingToolDiagnostics {false}
mutableprivate

Definition at line 155 of file SegmentEdgeInferenceAlg.h.

155{false};

The documentation for this class was generated from the following files: