9#include "GaudiKernel/SystemOfUnits.h"
12#include "CaloEvent/CaloTower.h"
27#include <unordered_map>
37 NodeKind kind{NodeKind::Muon};
38 std::array<float, 7> features{};
41 float energyLike{0.f};
46using SegmentList = std::vector<const xAOD::MuonSegment*>;
49 if (seg && !Acts::rangeContainsValue(segments, seg)) segments.push_back(seg);
54 std::set<unsigned int> uniqueLayers{};
55 for (
const MuonR4::SpacePointBucket::value_type&
sp : bucket) {
56 uniqueLayers.insert(
sorter.sectorLayerNum(*
sp));
58 return static_cast<uint16_t>(uniqueLayers.size());
63 std::ostringstream
sig;
64 sig << static_cast<const void*>(bucket.
msSector()) <<
'|'
67 << std::fixed << std::setprecision(3)
72void appendMuonSegmentNode(
const xAOD::MuonSegment& seg,
int bucketSector, std::vector<DVNodeAux>& nodes) {
78 node.kind = NodeKind::Muon;
79 node.features[0] =
static_cast<float>(posM.mag());
80 node.features[1] =
static_cast<float>(posM.theta());
81 node.features[2] =
static_cast<float>(posM.phi());
82 node.features[3] =
static_cast<float>(
dir.theta());
83 node.features[4] =
static_cast<float>(
dir.phi());
84 node.features[5] = 0.f;
86 node.eta =
static_cast<float>(posMm.eta());
87 node.phi =
static_cast<float>(posMm.phi());
88 node.energyLike = 0.f;
90 node.sector = bucketSector;
91 nodes.push_back(
node);
94bool hasName(
const std::vector<std::string>& names,
const std::string& needle) {
95 return Acts::rangeContainsValue(names, needle);
98std::string joinNames(
const std::vector<std::string>& names) {
99 std::ostringstream ostr;
100 for (std::size_t i = 0;
i <
names.size(); ++
i) {
101 if (i != 0u) ostr <<
", ";
107std::optional<Amg::Vector3D> firstIntersectionWithEnvelope(
const Amg::Vector3D& direction,
110 const float ux =
static_cast<float>(
direction.x());
111 const float uy =
static_cast<float>(
direction.y());
112 const float uz =
static_cast<float>(
direction.z());
113 std::optional<float> bestT{};
114 const float ur =
static_cast<float>(
direction.perp());
115 if (rMaxMm > 0.f && ur > 0.f) {
116 const float tBarrel = rMaxMm / ur;
117 const float zBarrel = tBarrel * uz;
118 if (std::abs(zBarrel) <= zMaxMm) bestT = tBarrel;
121 if (zMaxMm > 0.f && std::abs(uz) > 0.f) {
122 const float tEndcap = zMaxMm / std::abs(uz);
123 const float xEnd = tEndcap * ux;
124 const float yEnd = tEndcap * uy;
125 if (std::hypot(xEnd, yEnd) <= rMaxMm && (!bestT || tEndcap < *bestT)) {
130 if (!bestT)
return std::nullopt;
142 return StatusCode::FAILURE;
147 <<
" entries. The MuonBucketDump training samples use the "
148 <<
"default SegmentKey array with segments attached only to "
149 <<
"the first SpacePointKeys entry. For parity with training, "
150 <<
"configure SpacePointKeys=['MuonSpacePoints'] unless the "
151 <<
"training dump was produced with matching segment keys for all entries.");
157 <<
", TowerContainerKey="
165 return StatusCode::SUCCESS;
173 std::vector<std::string> names{};
174 Ort::AllocatorWithDefaultOptions allocator;
175 const std::size_t nInputs =
model().GetInputCount();
176 names.reserve(nInputs);
177 for (std::size_t i = 0; i < nInputs; ++i) {
178 auto name =
model().GetInputNameAllocated(i, allocator);
179 if (name) names.emplace_back(name.get());
185 std::vector<std::string> names{};
186 Ort::AllocatorWithDefaultOptions allocator;
187 const std::size_t nOutputs =
model().GetOutputCount();
188 names.reserve(nOutputs);
189 for (std::size_t i = 0; i < nOutputs; ++i) {
190 auto name =
model().GetOutputNameAllocated(i, allocator);
191 if (name) names.emplace_back(name.get());
207 <<
". I/O binding will be used.");
211 return StatusCode::SUCCESS;
217 if (!graphData.
graph || graphData.
graph->dataTensor.empty()) {
218 ATH_MSG_DEBUG(
"DV graph has no input tensors; skip inference for this event.");
219 return StatusCode::SUCCESS;
230 ATH_MSG_WARNING(
"DV graph is empty; no event-classifier output will be produced.");
231 return StatusCode::SUCCESS;
234 const auto xShape = graphData.
graph->dataTensor[0].GetTensorTypeAndShapeInfo().GetShape();
235 const auto edgeShape = graphData.
graph->dataTensor[1].GetTensorTypeAndShapeInfo().GetShape();
236 result.nNodes = !xShape.empty() && xShape[0] > 0 ?
static_cast<std::size_t
>(xShape[0]) : 0u;
237 result.nEdges = edgeShape.size() > 1 && edgeShape[1] > 0 ?
static_cast<std::size_t
>(edgeShape[1]) : 0u;
239 result.nMuonNodes =
static_cast<std::size_t
>(std::max<int64_t>(graphData.
spacePointsInBucket[0], 0));
240 result.nCaloNodes =
static_cast<std::size_t
>(std::max<int64_t>(graphData.
spacePointsInBucket[1], 0));
245 ATH_MSG_ERROR(
"DV inference finished without an output tensor.");
246 return StatusCode::FAILURE;
250 result.valid = std::isfinite(result.probability);
252 <<
" (muon=" << result.nMuonNodes <<
", calo=" << result.nCaloNodes
253 <<
"), E=" << result.nEdges
254 <<
", raw=" << result.rawOutput
255 <<
", probability=" << result.probability);
256 return StatusCode::SUCCESS;
261 graphData.
graph.reset();
267 graphData.
graph = std::make_unique<InferenceGraph>();
270 std::vector<DVNodeAux> nodes;
275 nodes.reserve(segments ? segments->
size() : 0u);
278 using SegmentsPerBucket_t =
279 std::unordered_map<const MuonR4::SpacePointBucket*, SegmentList>;
281 using SegmentsPerBucketSignature_t =
282 std::unordered_map<std::string, SegmentList>;
284 SegmentsPerBucket_t segmentsPerBucket{};
285 SegmentsPerBucketSignature_t segmentsPerBucketSignature{};
289 appendUniqueSegment(segmentsPerBucket[parentBucket], seg);
290 const std::string parentSig = bucketSignatureKey(*parentBucket);
291 if (!parentSig.empty()) appendUniqueSegment(segmentsPerBucketSignature[parentSig], seg);
293 std::size_t nSignatureMatchedBuckets = 0u;
299 const auto it = segmentsPerBucket.find(bucket);
301 if (it != segmentsPerBucket.end() && !it->second.empty()) {
302 matchedSegments = &it->second;
304 const std::string sig = bucketSignatureKey(*bucket);
305 const auto sigIt = sig.empty() ? segmentsPerBucketSignature.end()
306 : segmentsPerBucketSignature.find(sig);
307 if (sigIt != segmentsPerBucketSignature.end() && !sigIt->second.empty()) {
308 matchedSegments = &sigIt->second;
309 ++nSignatureMatchedBuckets;
312 if (!matchedSegments)
continue;
314 const int bucketSector = bucket->
msSector() ?
static_cast<int>(bucket->
msSector()->sector()) : -1;
315 const uint16_t bucketLayers = countLayersInBucket(*bucket);
317 <<
" sector=" << bucketSector
318 <<
" layers=" << bucketLayers
319 <<
" segments=" << matchedSegments->size());
322 appendMuonSegmentNode(*seg, bucketSector, nodes);
328 <<
" muon nodes from BucketDumper-style SpacePointBucket-associated segments"
329 <<
" (signature-matched filtered buckets=" << nSignatureMatchedBuckets <<
")");
332 if (segments && nodes.empty() &&
335 ATH_MSG_WARNING(
"No bucket-associated segments were found for DV graph building; "
336 "falling back to all segments from " <<
m_segmentKey.key()
337 <<
". This does not match the training converter exactly.");
340 appendMuonSegmentNode(*seg,
static_cast<int>(seg->
sector()), nodes);
346 ATH_MSG_WARNING(
"No bucket-associated segments were found for DV graph building. "
347 "Not falling back to all segments because that does not match the training converter.");
350 const std::size_t nMuonNodes = nodes.size();
356 nodes.reserve(nodes.size() + towers->size());
358 const float energyMeV =
static_cast<float>(tower->energy());
361 const float eta =
static_cast<float>(tower->eta());
362 const float phi =
static_cast<float>(tower->phi());
363 float minDR = std::numeric_limits<float>::max();
364 for (std::size_t i = 0; i < nMuonNodes; ++i) {
371 const Amg::Vector3D direction = Acts::makeDirectionFromPhiEta(
372 static_cast<double>(
phi),
static_cast<double>(
eta));
373 const std::optional<Amg::Vector3D> posMm =
375 if (!posMm)
continue;
380 node.kind = NodeKind::Calo;
381 node.features[0] =
static_cast<float>(posM.mag());
382 node.features[1] =
static_cast<float>(posM.theta());
383 node.features[2] =
static_cast<float>(posM.phi());
384 node.features[3] =
static_cast<float>(direction.theta());
385 node.features[4] =
static_cast<float>(direction.phi());
386 node.features[5] = energyMeV;
387 node.features[6] =
static_cast<float>(tower->size());
390 node.energyLike = energyMeV;
391 node.direction = direction;
392 node.sector =
static_cast<int>(
394 nodes.push_back(
node);
398 const std::size_t nCaloNodes = nodes.size() - nMuonNodes;
399 const std::size_t nNodes = nodes.size();
404 ATH_MSG_WARNING(
"No muon segment or calo tower nodes found. Skipping DV inference.");
405 return StatusCode::SUCCESS;
409 for (
const DVNodeAux&
node : nodes) {
411 node.features.begin(),
node.features.end());
415 std::vector<float> edgeAttr;
416 edgeAttr.reserve(2u * nMuonNodes * std::max<std::size_t>(nCaloNodes, 1u) *
kEdgeFeatureCount);
418 auto addEdge = [&graphData, &edgeAttr, maxEdges](std::size_t src,
421 const DVNodeAux& b) ->
bool {
422 if (maxEdges >= 0 &&
static_cast<int>(graphData.
srcEdges.size()) >= maxEdges)
return false;
424 const float dEta = b.eta -
a.eta;
425 const float cosAng = std::clamp(
static_cast<float>(
a.direction.dot(b.direction)), -1.f, 1.f);
426 std::array<float, kEdgeFeatureCount> attr{
427 b.energyLike -
a.energyLike,
431 (
a.sector == b.sector) ? 1.f : 0.f};
433 graphData.
srcEdges.push_back(
static_cast<int64_t
>(src));
434 graphData.
desEdges.push_back(
static_cast<int64_t
>(dst));
435 edgeAttr.insert(edgeAttr.end(), attr.begin(), attr.end());
439 bool edgeCapReached =
false;
440 for (std::size_t im = 0; im < nMuonNodes && !edgeCapReached; ++im) {
441 for (std::size_t ic = nMuonNodes; ic < nNodes; ++ic) {
444 if (!addEdge(im, ic, nodes[im], nodes[ic])) {
445 edgeCapReached =
true;
448 if (!addEdge(ic, im, nodes[ic], nodes[im])) {
449 edgeCapReached =
true;
455 const std::size_t nEdges = graphData.
srcEdges.size();
457 ATH_MSG_DEBUG(
"DV graph has no segment-tower edges and RequireEdges=True; skip inference.");
458 graphData.
graph.reset();
459 return StatusCode::SUCCESS;
463 ATH_MSG_ERROR(
"DV edge attribute size mismatch: E=" << nEdges
464 <<
" edge_attr.size=" << edgeAttr.size());
465 return StatusCode::FAILURE;
468 Ort::MemoryInfo memInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
469 std::vector<int64_t> nodeShape{
static_cast<int64_t
>(nNodes),
static_cast<int64_t
>(
kNodeFeatureCount)};
470 graphData.
graph->dataTensor.emplace_back(
471 Ort::Value::CreateTensor<float>(memInfo,
482 std::vector<int64_t> edgeIndexShape{2,
static_cast<int64_t
>(nEdges)};
483 graphData.
graph->dataTensor.emplace_back(
484 Ort::Value::CreateTensor<int64_t>(memInfo,
487 edgeIndexShape.data(),
488 edgeIndexShape.size()));
490 Ort::AllocatorWithDefaultOptions allocator;
491 std::vector<int64_t> edgeAttrShape{
static_cast<int64_t
>(nEdges),
static_cast<int64_t
>(
kEdgeFeatureCount)};
492 Ort::Value edgeAttrTensor = Ort::Value::CreateTensor(allocator,
493 edgeAttrShape.data(),
494 edgeAttrShape.size(),
495 ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT);
496 if (!edgeAttr.empty()) {
497 float* edgeAttrData = edgeAttrTensor.GetTensorMutableData<
float>();
498 std::copy(edgeAttr.begin(), edgeAttr.end(), edgeAttrData);
500 graphData.
graph->dataTensor.emplace_back(std::move(edgeAttrTensor));
502 std::vector<int64_t> nMuonShape{1};
503 graphData.
graph->dataTensor.emplace_back(
504 Ort::Value::CreateTensor<int64_t>(memInfo,
510 if (msgLvl(MSG::DEBUG)) {
511 ATH_MSG_DEBUG(
"Built DV graph: N=" << nNodes <<
" (muon=" << nMuonNodes
512 <<
", calo=" << nCaloNodes <<
"), E=" << nEdges
515 for (std::size_t i = 0; i < dumpNodes; ++i) {
516 std::ostringstream row;
517 row <<
"DVNode[" << i <<
"] kind=" << (nodes[i].kind == NodeKind::Muon ?
"muon" :
"calo") <<
":";
524 for (std::size_t e = 0; e < dumpEdges; ++e) {
538 return StatusCode::SUCCESS;
543 const std::vector<InputTensorSpec>& inputSpecs,
544 const std::vector<std::string>& outputNames)
const {
545 if (!graphData.
graph) {
547 return StatusCode::FAILURE;
549 if (inputSpecs.empty()) {
550 ATH_MSG_ERROR(
"No DV ONNX inputs were selected for inference.");
551 return StatusCode::FAILURE;
555 if (spec.tensorIndex >= graphData.
graph->dataTensor.size()) {
556 ATH_MSG_ERROR(
"Input " << spec.name <<
" requests tensor index " << spec.tensorIndex
557 <<
" but only " << graphData.
graph->dataTensor.size()
558 <<
" tensors were prepared.");
559 return StatusCode::FAILURE;
563 std::vector<const char*> inputNamePtrs{};
564 inputNamePtrs.reserve(inputSpecs.size());
566 inputNamePtrs.push_back(spec.name.c_str());
569 std::vector<const char*> outputNamePtrs{};
570 outputNamePtrs.reserve(outputNames.size());
571 for (
const std::string& name : outputNames) {
572 outputNamePtrs.push_back(name.c_str());
575 graphData.
graph->dataTensor.reserve(graphData.
graph->dataTensor.size() + outputNamePtrs.size());
577 Ort::RunOptions runOptions;
578 runOptions.SetRunLogSeverityLevel(ORT_LOGGING_LEVEL_ERROR);
581 Ort::IoBinding binding(
model());
583 binding.BindInput(spec.name.c_str(), graphData.
graph->dataTensor[spec.tensorIndex]);
585 Ort::MemoryInfo cpuOut = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
586 for (
const char* outName : outputNamePtrs) {
587 binding.BindOutput(outName, cpuOut);
590 model().Run(runOptions, binding);
591 binding.SynchronizeOutputs();
593 std::vector<Ort::Value> outputs = binding.GetOutputValues();
594 if (outputs.empty()) {
596 return StatusCode::FAILURE;
600 float* outData = outputs[0].GetTensorMutableData<
float>();
601 const std::size_t outSize = outputs[0].GetTensorTypeAndShapeInfo().GetElementCount();
602 for (std::size_t i = 0; i < outSize; ++i) {
603 if (!std::isfinite(outData[i])) {
604 ATH_MSG_WARNING(
"Non-finite DV prediction detected at " << i <<
" -> set to -100.");
610 for (
auto& v : outputs) {
611 graphData.
graph->dataTensor.emplace_back(std::move(v));
613 return StatusCode::SUCCESS;
616 std::vector<Ort::Value> orderedInputs{};
617 orderedInputs.reserve(inputSpecs.size());
619 orderedInputs.emplace_back(std::move(graphData.
graph->dataTensor[spec.tensorIndex]));
622 std::vector<Ort::Value> outputs =
623 model().Run(runOptions,
624 inputNamePtrs.data(),
625 orderedInputs.data(),
626 inputNamePtrs.size(),
627 outputNamePtrs.data(),
628 outputNamePtrs.size());
630 if (outputs.empty()) {
632 return StatusCode::FAILURE;
636 << outputs[0].GetTensorTypeAndShapeInfo().GetElementCount());
639 float* outData = outputs[0].GetTensorMutableData<
float>();
640 const std::size_t outSize = outputs[0].GetTensorTypeAndShapeInfo().GetElementCount();
641 for (std::size_t i = 0; i < outSize; ++i) {
642 if (!std::isfinite(outData[i])) {
643 ATH_MSG_WARNING(
"Non-finite DV prediction detected at " << i <<
" -> set to -100.");
649 for (
auto& v : outputs) {
650 graphData.
graph->dataTensor.emplace_back(std::move(v));
652 return StatusCode::SUCCESS;
657 if (availableInputs.empty()) {
659 return StatusCode::FAILURE;
662 ATH_MSG_DEBUG(
"DV ONNX model inputs: " << joinNames(availableInputs));
669 std::vector<InputTensorSpec> inputSpecs{};
672 auto addIfPresent = [&availableInputs, &inputSpecs](
const std::string& name, std::size_t tensorIndex) {
673 if (hasName(availableInputs, name)) {
680 const bool hasNodeInput = addIfPresent(nodeName, 0u);
681 const bool hasEdgeIndexInput = addIfPresent(edgeIndexName, 1u);
682 const bool hasEdgeAttrInput = addIfPresent(edgeAttrName, 2u);
683 const bool hasNMuonNodesInput = addIfPresent(nMuonNodesName, 3u);
685 if (!hasNodeInput || !hasEdgeIndexInput) {
686 ATH_MSG_ERROR(
"DV ONNX model is missing required inputs. Expected at least "
687 << nodeName <<
" and " << edgeIndexName
688 <<
"; model inputs are: " << joinNames(availableInputs));
689 return StatusCode::FAILURE;
692 if (!hasEdgeAttrInput) {
693 ATH_MSG_DEBUG(
"DV ONNX model has no input named " << edgeAttrName
694 <<
"; not binding edge_attr. This is expected for exports where "
695 <<
"the architecture does not consume edge attributes and ONNX pruned the input.");
697 if (!hasNMuonNodesInput) {
698 ATH_MSG_DEBUG(
"DV ONNX model has no input named " << nMuonNodesName
699 <<
"; not binding n_muon_nodes. This is expected only if the exported "
700 <<
"model does not need model-side muon/calo normalization.");
703 for (
const std::string& inputName : availableInputs) {
704 if (inputName != nodeName && inputName != edgeIndexName &&
705 inputName != edgeAttrName && inputName != nMuonNodesName) {
706 ATH_MSG_ERROR(
"DV ONNX model has unsupported input " << inputName
707 <<
". Configure the input-name properties or update the tool mapping.");
708 return StatusCode::FAILURE;
712 std::vector<std::string> outputNames{};
716 }
else if (!availableOutputs.empty()) {
718 <<
"; using first model output " << availableOutputs.front() <<
".");
719 outputNames.push_back(availableOutputs.front());
722 return StatusCode::FAILURE;
730 const float* data = output.GetTensorData<
float>();
731 const auto shapeInfo = output.GetTensorTypeAndShapeInfo();
732 const std::size_t nElem = shapeInfo.GetElementCount();
733 if (nElem == 0 || data ==
nullptr)
return std::numeric_limits<float>::quiet_NaN();
746 const float z0 = data[0] - std::max(data[0], data[1]);
747 const float z1 = data[1] - std::max(data[0], data[1]);
748 const float e0 = std::exp(z0);
749 const float e1 = std::exp(z1);
750 return e1 / (e0 + e1);
754 <<
" elements; using element 0 with SingleOutputMode=" <<
m_singleOutputMode.value());
Scalar eta() const
pseudorapidity method
Scalar phi() const
phi method
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_WARNING(x)
Handle class for reading from StoreGate.
Storable container class for CaloTower.
Data class for calorimeter cell towers.
size_type size() const noexcept
Returns the number of elements in the collection.
int8_t side() const
Returns the side of the MS-sector 1 -> A side ; -1 -> C side.
int sector() const
Returns the sector of the MS-sector.
unsigned msSector() const
Returns the ms sector corresponding to the expanded sector.
: The muon space point bucket represents a collection of points that will bre processed together in t...
const MuonGMR4::SpectrometerSector * msSector() const
returns th associated muonChamber
double coveredMin() const
lower interval value covered by the bucket
double coveredMax() const
upper interval value covered by the bucket
The SpacePointPerLayerSorter sort two given space points by their layer Identifier.
Property holding a SG store/key/clid from which a ReadHandle is made.
float numberDoF() const
Returns the numberDoF.
Amg::Vector3D direction() const
Returns the direction as Amg::Vector.
Amg::Vector3D position() const
Returns the position as Amg::Vector.
Eigen::Matrix< double, 3, 1 > Vector3D
T wrapToPi(T phi)
Wrap angle in radians to [-pi, pi].
T deltaPhi(T phiA, T phiB)
Return difference phiA - phiB in range [-pi, pi].
SessionBackend sessionBackend(const SessionToolHandle &sessionTool)
DataVector< SpacePointBucket > SpacePointContainer
Abrivation of the space point container type.
const Segment * detailedSegment(const xAOD::MuonSegment &seg)
Helper function to navigate from the xAOD::MuonSegment to the MuonR4::Segment.
NRpcCablingAlg reads raw condition data and writes derived condition data to the condition store.
const T * get(const ReadCondHandleKey< T > &key, const EventContext &ctx)
Convenience function to retrieve an object given a ReadCondHandleKey.
const Amg::Vector3D & direction() const
Method to retrieve the direction at the Intersection.
double deltaR(double rapidity1, double phi1, double rapidity2, double phi2)
from bare bare rapidity,phi
MuonSegmentContainer_v1 MuonSegmentContainer
Definition of the current "MuonSegment container version".
MuonSegment_v1 MuonSegment
Reference the current persistent version:
Helper for azimuthal angle calculations.
Helper struct to ship the Graph from the space point buckets to ONNX.
FeatureVec_t featureLeaves
Vector containing all features.
EdgeCounterVec_t edgeIndexPacked
Packed edge index buffer (kept alive for ONNX tensors that reference it) This stores [srcEdges,...
std::unique_ptr< InferenceGraph > graph
Pointer to the graph to be parsed to ONNX.
EdgeCounterVec_t srcEdges
Vector encoding the source index of the.
EdgeCounterVec_t desEdges
Vect.
NodeConnectVec_t spacePointsInBucket
Vector keeping track of how many space points are in each parsed bucket.