136 <<
"' = " << segments->
size());
139 std::size_t truthSegs = 0, bkgSegs = 0;
148 std::vector<SegmentEdgeScore>
scores{};
151 <<
": built graph with nodes=" << graph.
nNodes
152 <<
", edges=" << graph.
nEdges);
156 std::vector<int32_t> nodeTruthId;
158 std::unordered_map<int32_t, double> truthIdToAbsEta;
160 nodeTruthId.assign(graph.
nNodes, -1);
161 std::size_t truthSegs = 0, bkgSegs = 0;
166 nodeTruthId[
node] =
static_cast<int32_t
>(truthPart->index());
167 truthIdToAbsEta.emplace(nodeTruthId[
node], std::abs(truthPart->
eta()));
179 float minProb = std::numeric_limits<float>::max();
180 float maxProb = std::numeric_limits<float>::lowest();
182 minProb = std::min(minProb, score.probability);
183 maxProb = std::max(maxProb, score.probability);
186 <<
": edge scores=" <<
scores.size()
187 <<
", prob range=[" << minProb <<
", " << maxProb <<
"]");
190 <<
": no edge scores produced");
195 std::unordered_map<std::uint64_t, float> pairProbability;
196 pairProbability.reserve(
scores.size());
198 if (score.src >= graph.
nNodes || score.dst >= graph.
nNodes ||
199 score.src == score.dst) {
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);
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];
233 using WeightedEdge = std::pair<std::uint64_t, float>;
234 const auto betterWeightedEdge = [](
const WeightedEdge& first,
235 const WeightedEdge& second) {
237 first.second, second.second);
238 if (probabilityOrder != 0) {
239 return probabilityOrder < 0;
241 return first.first < second.first;
243 std::vector<std::vector<WeightedEdge>> edgesByNode(graph.
nNodes);
244 std::size_t thresholdPairs = 0;
245 for (
const auto& [key, probability] : pairProbability) {
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);
254 std::size_t thresholdedNodes = 0;
255 for (
const std::vector<WeightedEdge>& nodeEdges : edgesByNode) {
256 thresholdedNodes += !nodeEdges.empty();
259 std::vector<Muon::MuonStationIndex::LayerIndex> nodeLayer(graph.
nNodes);
264 std::unordered_map<std::uint64_t, unsigned char> nominations;
265 nominations.reserve(thresholdPairs);
267 std::vector<WeightedEdge>& nodeEdges = edgesByNode[
node];
268 std::sort(nodeEdges.begin(), nodeEdges.end(), betterWeightedEdge);
269 if (maxEdgesPerLayer != 0) {
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;
283 for (
const WeightedEdge& edge : nodeEdges) {
284 ++nominations[edge.first];
288 std::size_t mutualTopKPairs = 0;
289 std::size_t oneSidedTopKPairs = 0;
290 for (
const auto& [_,
count] : nominations) {
298 std::unordered_set<std::uint64_t> selectedPairKeys;
299 selectedPairKeys.reserve(thresholdPairs);
302 std::vector<WeightedEdge> acceptedPairs;
303 acceptedPairs.reserve(thresholdPairs);
304 for (
const auto& [key, probability] : pairProbability) {
306 acceptedPairs.emplace_back(key, probability);
308 std::sort(acceptedPairs.begin(), acceptedPairs.end(), betterWeightedEdge);
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)) {
318 selectedPairKeys.insert(edge.first);
323 for (
const auto& [key,
count] : nominations) {
325 selectedPairKeys.insert(key);
331 std::size_t orphanRecoveryPairs = 0;
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;
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;
352 std::vector<std::uint64_t> selectedPairs{selectedPairKeys.begin(),
353 selectedPairKeys.end()};
354 std::sort(selectedPairs.begin(), selectedPairs.end());
359 for (
const auto& [key, probability] : pairProbability) {
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 =
367 ++m_sumTrueCrossLayerEdges[region];
368 if (selectedPairKeys.contains(key)) ++m_sumTrueCrossLayerSelected[region];
374 std::vector<float> nodeBestSelectedScore;
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);
390 const auto layerOf = [&graph](std::size_t
node) {
393 std::vector<float> nodeBestCrossLayerScore;
394 std::vector<unsigned int> nodeCrossLayerEdges;
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);
406 ++nodeCrossLayerEdges[first];
407 ++nodeCrossLayerEdges[second];
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;
422 std::unordered_map<std::size_t, std::vector<std::size_t>> byRoot;
423 byRoot.reserve(graph.
nNodes);
425 if (activeNode[
node]) byRoot[components.find(
node)].push_back(
node);
429 std::vector<std::vector<std::size_t>> componentNodes;
430 componentNodes.reserve(byRoot.size());
431 for (
auto& [_, nodes] : byRoot) {
433 componentNodes.push_back(std::move(nodes));
435 std::ranges::sort(componentNodes,
436 [](
const auto& first,
const auto& second) {
437 return first.front() < second.front();
445 std::vector<std::uint8_t> keptNode(graph.
nNodes,
false);
446 std::vector<std::uint8_t> nodeDropReason;
448 nodeDropReason.assign(graph.
nNodes, kReasonNotSelected);
453 std::unordered_map<int32_t, std::unordered_set<unsigned>> muonComponents;
454 std::unordered_map<int32_t, bool> muonEverMixed;
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 {
460 if (
size < 10)
return 3;
461 if (
size < 20)
return 4;
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;
475 const auto isBetterNode = [&](std::size_t candidate, std::size_t incumbent) {
482 return candidate < incumbent;
485 for (
const std::vector<std::size_t>& rawNodes : componentNodes) {
486 topologyNodes += rawNodes.size();
487 std::vector<std::size_t> retained = rawNodes;
492 std::unordered_map<int, std::size_t> bestByChamber;
493 bestByChamber.reserve(rawNodes.size());
494 for (
const std::size_t
node : rawNodes) {
497 const auto found = bestByChamber.find(chamber);
498 if (found == bestByChamber.end() || isBetterNode(
node, found->second)) {
499 bestByChamber[chamber] =
node;
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();
511 for (
const std::size_t
node : rawNodes) {
512 nodeDropReason[
node] = kReasonChamberDedup;
514 for (
const std::size_t
node : retained) {
515 nodeDropReason[
node] = kReasonComponentRejected;
520 nodesRejectedByMinComponent += retained.size();
521 ++rejectedComponents;
527 std::vector<std::size_t> rankedNodes{retained};
530 int bestRank = std::numeric_limits<int>::max();
531 for (
const std::size_t
node : rankedNodes) {
537 std::erase_if(rankedNodes, [&](std::size_t
node) {
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);
554 std::ranges::sort(rankedNodes, isBetterNode);
559 rankedNodes.resize(nAnchors);
561 if (rankedNodes.empty()) {
562 ++rejectedComponents;
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]);
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();
579 isAnchor ?
static_cast<unsigned int>(anchorIt - rankedNodes.begin()) + 1 : 0u};
580 keptNode[
node] =
true;
582 retainedNodes += retained.size();
583 anchors += rankedNodes.size();
588 std::unordered_set<int32_t> componentTruthIds;
589 for (
const std::size_t
node : retained) {
590 if (nodeTruthId[
node] >= 0) componentTruthIds.insert(nodeTruthId[
node]);
592 const std::size_t outcome = componentTruthIds.empty() ? 2
593 : componentTruthIds.size() == 1 ? 0 : 1;
594 ++m_groupPurity.componentsByOutcome[outcome];
595 ++m_groupPurity.componentSizeHist[sizeBin(retained.size())];
600 float strongest = -1.f;
601 for (
const std::size_t
node : rawNodes) {
602 strongest = std::max(strongest, nodeBestSelectedScore[
node]);
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) {
612 const std::size_t layerBin =
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];
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;
631 std::min<std::size_t>(crossEdges, 3)];
632 if (crossScore < 0.05f) {
633 if (outcome == 0) weakCleanMuons.push_back(*componentTruthIds.begin());
635 for (
const int32_t
id : componentTruthIds) ++strongComponentsPerMuon[id];
638 const bool mixed = componentTruthIds.size() >= 2;
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)) {}
647 for (
const int32_t
id : componentTruthIds) {
648 muonComponents[id].insert(componentId);
649 if (mixed) muonEverMixed[id] =
true;
655 std::size_t truthSegs = 0, bkgSegs = 0;
657 if (!keptNode[
node])
continue;
658 nodeTruthId[
node] >= 0 ? ++truthSegs : ++bkgSegs;
663 std::vector<unsigned char> thresholded(graph.
nNodes, 0);
665 thresholded[
node] = !edgesByNode[
node].empty();
668 keptNode, nodeDropReason);
670 for (
const int32_t
id : weakCleanMuons) {
671 ++(strongComponentsPerMuon[id] > 0 ? m_groupPurity.weakCleanWithOther : m_groupPurity.weakCleanAlone);
676 auto connectedSegments =
677 std::make_unique<ConstDataVector<xAOD::MuonSegmentContainer>>(
679 connectedSegments->reserve(graph.
nNodes);
685 const std::size_t nConnectedSegments = connectedSegments->size();
690 <<
": wrote " << nConnectedSegments
691 <<
" ML-connected segment(s) to '"
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
708 <<
", seedAnchors=" << anchors
709 <<
", rejectedComponents=" << rejectedComponents
712 <<
", orphanRecoveryPairs=" << orphanRecoveryPairs
730 return StatusCode::SUCCESS;
735 const std::unordered_map<std::uint64_t, float>& pairProbability,
736 const std::vector<unsigned char>& thresholded,
737 const std::vector<std::uint8_t>& keptNode,
738 const std::vector<unsigned char>& nodeDropReason)
const {
741 const std::size_t nInput = segments.
size();
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.");
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;
758 std::uint32_t inputIndex = 0;
761 truthPart[inputIndex] = part;
762 truthId[inputIndex] =
static_cast<std::int32_t
>(part->index());
763 byTruth[truthId[inputIndex]].push_back(inputIndex);
768 const auto isRetained = [&](std::size_t input) {
770 return node >= 0 && keptNode[
static_cast<std::size_t
>(
node)];
776 const auto rankOf = [](
PairGate gate) ->
unsigned char {
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);
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);
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);
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);
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())));
831 ++muonsSeedable[region];
834 ++muonsSeedLost[region];
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]];
850 if (members.size() == 1) {
851 cat = kNoPartnerSingleton;
852 }
else if (bestRank[i] == 0) {
853 cat = kNoPartnerSameChamber;
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;
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;
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];
878 if (cat != kBelowThreshold)
continue;
879 if (bestScoredPair[i] >= 0) {
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];
887 segments[i]->chamberIndex()));
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];
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)];
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];
937 if (!
msgLvl(MSG::DEBUG))
return StatusCode::SUCCESS;
939 "SegmentEdgeInferenceAlg post-ONNX selection summary (job-summed): "
944 <<
", oneSidedTopKPairs(rejected by RequireMutualTopKEdges="
948 <<
"; a thresholded edge is dropped here purely by rank, "
949 "independent of PairGateThreshold)"
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): "
975 <<
"; edge-level (same truth-particle index on both endpoints) at "
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"};
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));
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());
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());
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();
1055 printByOutcome(
"strongest selected-edge score (<0.05/0.05-0.1/0.1-0.3/0.3-0.5/>=0.5)",
1057 printByOutcome(
"retained segments (2/3/4/5-9/10-19/20+)",
1059 printByOutcome(
"distinct layers (1/2/3/4+)",
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)",
1064 printByOutcome(
"number of selected cross-layer edges, components with >=2 layers (0/1/2/3+)",
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());
1071 const std::uint64_t worstMixed = m_groupPurity.worstMixedPacked.load();
1073 << (worstMixed >> 32) <<
" distinct truth muons in "
1074 << (worstMixed & 0xffffffffull) <<
" segments"
1075 << (worstMixed == 0 ?
" (no mixed components found)" :
""));
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());
1093 return StatusCode::SUCCESS;