244 std::map<SegmentBucketKey, std::vector<const xAOD::MuonSegment*>>
247 segmentsByBucket[segmentBucketKey(*segment)].push_back(segment);
252 std::unordered_set<const xAOD::MuonSegment*> retainedSegments;
253 retainedSegments.reserve(segments.
size());
254 for (
auto& [_, bucketSegments] : segmentsByBucket) {
255 std::ranges::sort(bucketSegments, betterSegment);
257 ? bucketSegments.size()
258 : std::min<std::size_t>(
259 bucketSegments.size(),
261 retainedSegments.insert(bucketSegments.begin(),
262 bucketSegments.begin() + nKeep);
265 std::vector<Amg::Vector3D> pos;
266 std::vector<Amg::Vector3D> dir;
267 std::vector<BucketSegmentFeatures> bucket;
268 pos.reserve(retainedSegments.size());
269 dir.reserve(retainedSegments.size());
270 bucket.reserve(retainedSegments.size());
273 if (!retainedSegments.contains(segment))
continue;
277 const SegmentBucketKey key = segmentBucketKey(*segment);
278 const auto bucketIt = segmentsByBucket.find(key);
279 const int multiplicity =
280 bucketIt == segmentsByBucket.end()
282 :
static_cast<int>(bucketIt->second.size());
284 const int chamberIndex =
static_cast<int>(segment->chamberIndex());
286 const int sector = segment->sector();
289 pos.emplace_back(position / Gaudi::Units::m);
290 dir.emplace_back(direction);
292 chamberIndex, layers, sector, multiplicity});
295 nodeFeatureValue(featureId, pos.back(), dir.back(), bucket.back()));
300 if (pos.size() != graph.
nNodes || dir.size() != graph.
nNodes ||
301 bucket.size() != graph.
nNodes) {
302 ATH_MSG_ERROR(
"Inconsistent vector sizes during graph building: nodes="
303 << graph.
nNodes <<
", pos=" << pos.size()
304 <<
", dir=" << dir.size() <<
", bucket=" << bucket.size());
305 return StatusCode::FAILURE;
310 return StatusCode::SUCCESS;
313 const auto wrapRegularSector = [&](
int sector) {
328 std::unordered_map<int, std::vector<std::size_t>> nodesBySector;
329 nodesBySector.reserve(graph.
nNodes);
331 nodesBySector[wrapRegularSector(bucket[
node].sector)].push_back(
node);
334 std::unordered_map<int, std::vector<int>> targetSectorsBySourceSector;
335 targetSectorsBySourceSector.reserve(nodesBySector.size());
336 std::size_t sectorLocalEdgeUpperBound = 0;
337 for (
const auto& [sourceSector, sourceNodes] : nodesBySector) {
338 std::vector<int> targetSectors;
342 targetSectors.push_back(wrapRegularSector(sourceSector + delta));
344 for (
const int targetSector : targetSectors) {
345 const auto found = nodesBySector.find(targetSector);
346 if (found == nodesBySector.end())
continue;
347 sectorLocalEdgeUpperBound += sourceNodes.size() * found->second.size();
348 if (targetSector == sourceSector) {
349 sectorLocalEdgeUpperBound -= sourceNodes.size();
352 targetSectorsBySourceSector.emplace(sourceSector,
353 std::move(targetSectors));
363 struct UndirectedEdge {
364 std::size_t first{0};
365 std::size_t second{0};
372 const auto betterEdge = [](
const UndirectedEdge& first,
373 const UndirectedEdge& second) {
375 first.cosAngle, second.cosAngle);
380 const int distanceOrder =
382 if (distanceOrder != 0) {
383 return distanceOrder < 0;
385 if (first.first != second.first)
return first.first < second.first;
386 return first.second < second.second;
388 const auto edgeKey = [](
const UndirectedEdge& edge) {
389 return (
static_cast<std::uint64_t
>(edge.first) << 32) |
390 static_cast<std::uint64_t
>(edge.second);
393 const unsigned int maxEdgesPerNode =
395 const unsigned int maxEdgesPerTargetChamber =
397 const bool usePreInferenceSelection =
398 maxEdgesPerNode != 0 || maxEdgesPerTargetChamber != 0;
399 std::vector<std::vector<UndirectedEdge>> bestEdgesByNode;
400 if (usePreInferenceSelection) {
401 bestEdgesByNode.resize(graph.
nNodes);
402 const unsigned int reservePerNode =
403 maxEdgesPerNode != 0 ? maxEdgesPerNode : maxEdgesPerTargetChamber;
404 for (std::vector<UndirectedEdge>& edges : bestEdgesByNode) {
405 edges.reserve(reservePerNode);
408 graph.
edgeIndex.reserve(2 * sectorLocalEdgeUpperBound);
411 const auto appendDirectedPair = [&](
const UndirectedEdge& edge) {
412 graph.
edgeIndex.push_back(
static_cast<int64_t
>(edge.first));
413 graph.
edgeIndex.push_back(
static_cast<int64_t
>(edge.second));
416 {edge.dx, edge.dy, edge.dz, edge.distance, edge.cosAngle,
417 float(bucket[edge.first].chamberIndex ==
418 bucket[edge.second].chamberIndex),
419 float(bucket[edge.first].sector == bucket[edge.second].sector)});
421 graph.
edgeIndex.push_back(
static_cast<int64_t
>(edge.second));
422 graph.
edgeIndex.push_back(
static_cast<int64_t
>(edge.first));
425 {-edge.dx, -edge.dy, -edge.dz, edge.distance, edge.cosAngle,
426 float(bucket[edge.first].chamberIndex ==
427 bucket[edge.second].chamberIndex),
428 float(bucket[edge.first].sector == bucket[edge.second].sector)});
431 const auto retainForNode = [&](std::size_t
node,
432 const UndirectedEdge& candidate) {
433 std::vector<UndirectedEdge>& retained = bestEdgesByNode[
node];
434 const std::size_t other = candidate.first ==
node ? candidate.second
436 const int targetChamber = bucket[other].chamberIndex;
438 if (maxEdgesPerTargetChamber != 0) {
439 unsigned int sameChamberCount = 0;
440 auto worstSameChamber = retained.end();
441 for (
auto it = retained.begin(); it != retained.end(); ++it) {
442 const std::size_t retainedOther =
443 it->first ==
node ? it->second : it->first;
444 if (bucket[retainedOther].chamberIndex != targetChamber)
continue;
446 if (worstSameChamber == retained.end() ||
447 betterEdge(*worstSameChamber, *it)) {
448 worstSameChamber = it;
451 if (sameChamberCount >= maxEdgesPerTargetChamber) {
452 if (!betterEdge(candidate, *worstSameChamber))
return;
453 *worstSameChamber = candidate;
455 retained.push_back(candidate);
458 retained.push_back(candidate);
461 if (maxEdgesPerNode != 0 && retained.size() > maxEdgesPerNode) {
462 auto worst = retained.begin();
463 for (
auto it = std::next(retained.begin()); it != retained.end(); ++it) {
464 if (betterEdge(*worst, *it)) worst = it;
466 retained.erase(worst);
470 std::size_t candidatePairs = 0;
471 for (std::size_t first = 0; first < graph.
nNodes; ++first) {
472 const auto sectorsIt =
473 targetSectorsBySourceSector.find(wrapRegularSector(bucket[first].sector));
474 if (sectorsIt == targetSectorsBySourceSector.end())
continue;
475 for (
const int sector : sectorsIt->second) {
476 const auto targetIt = nodesBySector.find(sector);
477 if (targetIt == nodesBySector.end())
continue;
479 for (
const std::size_t second : targetIt->second) {
481 if (second <= first)
continue;
482 if (sectorDistance(bucket[first].sector, bucket[second].sector,
488 bucket[first].chamberIndex == bucket[second].chamberIndex) {
491 const float cosAngle =
static_cast<float>(dir[first].dot(dir[second]));
495 const UndirectedEdge candidate{
498 static_cast<float>(delta.x()),
499 static_cast<float>(delta.y()),
500 static_cast<float>(delta.z()),
501 static_cast<float>(delta.mag()),
505 if (!usePreInferenceSelection) {
506 appendDirectedPair(candidate);
508 retainForNode(first, candidate);
509 retainForNode(second, candidate);
514 std::size_t retainedPairs = candidatePairs;
515 if (usePreInferenceSelection) {
516 const unsigned int selectedReservePerNode =
517 maxEdgesPerNode != 0 ? maxEdgesPerNode : maxEdgesPerTargetChamber;
518 std::unordered_set<std::uint64_t> selectedKeys;
519 selectedKeys.reserve(graph.
nNodes * selectedReservePerNode);
520 std::vector<UndirectedEdge> selectedEdges;
521 selectedEdges.reserve(graph.
nNodes * selectedReservePerNode);
523 for (
const std::vector<UndirectedEdge>& nodeEdges : bestEdgesByNode) {
524 for (
const UndirectedEdge& edge : nodeEdges) {
525 if (selectedKeys.insert(edgeKey(edge)).second) {
526 selectedEdges.push_back(edge);
530 std::sort(selectedEdges.begin(), selectedEdges.end(),
531 [](
const UndirectedEdge& first,
532 const UndirectedEdge& second) {
533 if (first.first != second.first) {
534 return first.first < second.first;
536 return first.second < second.second;
539 retainedPairs = selectedEdges.size();
540 graph.
edgeIndex.reserve(4 * retainedPairs);
542 for (
const UndirectedEdge& edge : selectedEdges) {
543 appendDirectedPair(edge);
546 graph.nEdges = graph.edgeIndex.size() / 2;
547 const std::size_t nodesBeforeIsolatedNodeDrop = graph.nNodes;
548 if (m_dropIsolatedNodesBeforeInference.value() && graph.nEdges != 0) {
549 std::vector<unsigned char> active(graph.nNodes, 0);
550 for (
const int64_t
index : graph.edgeIndex) {
551 active[
static_cast<std::size_t
>(
index)] = 1;
553 const std::size_t activeNodes =
554 std::count(active.begin(), active.end(),
static_cast<unsigned char>(1));
555 if (activeNodes != graph.nNodes) {
556 std::vector<std::size_t> oldToNew(graph.nNodes, graph.nNodes);
557 std::vector<const xAOD::MuonSegment*> compactedSegments;
558 std::vector<float> compactedNodeFeatures;
559 compactedSegments.reserve(activeNodes);
560 compactedNodeFeatures.reserve(activeNodes * kNodeFeatureCount);
561 for (std::size_t oldNode = 0; oldNode < graph.nNodes; ++oldNode) {
562 if (!active[oldNode])
continue;
563 oldToNew[oldNode] = compactedSegments.size();
564 compactedSegments.push_back(graph.segments[oldNode]);
565 const auto featureBegin = graph.nodeFeatures.begin() +
566 oldNode * kNodeFeatureCount;
567 compactedNodeFeatures.insert(compactedNodeFeatures.end(),
569 featureBegin + kNodeFeatureCount);
571 for (int64_t& index : graph.edgeIndex) {
572 index =
static_cast<int64_t
>(oldToNew[
static_cast<std::size_t
>(
index)]);
574 graph.segments = std::move(compactedSegments);
575 graph.nodeFeatures = std::move(compactedNodeFeatures);
576 graph.nNodes = activeNodes;
579 ATH_MSG_DEBUG(
"buildGraph: input segments=" << segments.size()
580 <<
", kept nodes=" << graph.nNodes
581 <<
", nodes before isolated-node drop=" << nodesBeforeIsolatedNodeDrop
582 <<
", bucket cap=" << m_maxSegmentsPerBucket.value()
583 <<
", candidate pairs=" << candidatePairs
584 <<
", retained pairs=" << retainedPairs
585 <<
", built directed edges=" << graph.nEdges
586 <<
", pre-inference node cap=" << m_maxEdgesPerNodeBeforeInference.value()
587 <<
", per-target-chamber cap=" << maxEdgesPerTargetChamber
588 <<
", drop same chamber=" << m_dropSameChamberEdgesBeforeInference.value()
589 <<
", drop isolated nodes=" << m_dropIsolatedNodesBeforeInference.value()
590 <<
", sector-local reserve=" << sectorLocalEdgeUpperBound);
591 return StatusCode::SUCCESS;
707 const EventContext& ctx,
709 const std::vector<SegmentEdgeScore>& scores)
const {
716 return StatusCode::SUCCESS;
722 scores.size() != graph.
nEdges) {
723 ATH_MSG_ERROR(
"Cannot write segment-edge debug dump: inconsistent graph/output sizes"
724 <<
" nodes=" << graph.
nNodes
726 <<
" edges=" << graph.
nEdges
727 <<
" edgeIndex=" << graph.
edgeIndex.size()
729 <<
" scores=" << scores.size());
730 return StatusCode::FAILURE;
733 nlohmann::json
x = nlohmann::json::array();
734 x.get_ref<nlohmann::json::array_t&>().reserve(graph.
nodeFeatures.size());
736 x.push_back(std::isfinite(value) ? nlohmann::json(value)
737 : nlohmann::json(
nullptr));
740 nlohmann::json edgeIndex = nlohmann::json::array();
741 edgeIndex.get_ref<nlohmann::json::array_t&>().reserve(graph.
nEdges * 2);
743 for (std::size_t edge = 0; edge < graph.
nEdges; ++edge) {
744 edgeIndex.push_back(graph.
edgeIndex[2 * edge]);
746 for (std::size_t edge = 0; edge < graph.
nEdges; ++edge) {
747 edgeIndex.push_back(graph.
edgeIndex[2 * edge + 1]);
750 nlohmann::json edgeAttr = nlohmann::json::array();
751 edgeAttr.get_ref<nlohmann::json::array_t&>().reserve(graph.
edgeFeatures.size());
753 edgeAttr.push_back(std::isfinite(value) ? nlohmann::json(value)
754 : nlohmann::json(
nullptr));
757 nlohmann::json logits = nlohmann::json::array();
758 nlohmann::json probabilities = nlohmann::json::array();
759 nlohmann::json edgeSrc = nlohmann::json::array();
760 nlohmann::json edgeDst = nlohmann::json::array();
761 logits.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
762 probabilities.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
763 edgeSrc.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
764 edgeDst.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
766 edgeSrc.push_back(score.src);
767 edgeDst.push_back(score.dst);
768 logits.push_back(std::isfinite(score.logit) ? nlohmann::json(score.logit)
769 : nlohmann::json(
nullptr));
770 probabilities.push_back(std::isfinite(score.probability)
771 ? nlohmann::json(score.probability)
772 : nlohmann::json(
nullptr));
775 std::ofstream out{
m_debugDumpFile.value(), std::ios::out | std::ios::app};
777 ATH_MSG_ERROR(
"Could not append to segment-edge debug dump file: "
779 return StatusCode::FAILURE;
782 const unsigned int dumpIndex =
784 nlohmann::ordered_json event;
785 event[
"record_type"] =
"event";
786 event[
"format_version"] = 1;
787 event[
"dump_index"] = dumpIndex;
788 event[
"run_number"] = ctx.eventID().run_number();
789 event[
"lumi_block"] = ctx.eventID().lumi_block();
790 event[
"event_number"] = ctx.eventID().event_number();
791 event[
"slot"] = ctx.slot();
792 event[
"n_nodes"] = graph.
nNodes;
793 event[
"n_edges"] = graph.
nEdges;
795 event[
"edge_index_shape"] = {2, graph.
nEdges};
797 event[
"logits_shape"] = {graph.
nEdges};
798 event[
"x"] = std::move(
x);
799 event[
"edge_index"] = std::move(edgeIndex);
800 event[
"edge_attr"] = std::move(edgeAttr);
801 event[
"edge_src"] = std::move(edgeSrc);
802 event[
"edge_dst"] = std::move(edgeDst);
803 event[
"logits"] = std::move(logits);
804 event[
"probabilities"] = std::move(probabilities);
805 out <<
event.dump() <<
'\n';
810 return StatusCode::SUCCESS;