6#include "GaudiKernel/SystemOfUnits.h"
7#include <nlohmann/json.hpp>
17#include <unordered_map>
18#include <unordered_set>
21using SegmentGroupKey = std::tuple<int, int, int>;
36inline int sectorDistance(
int a,
int b,
int mod) {
37 int d = std::abs(
a - b);
38 return mod > 0 ? std::min(d, mod - d) :
d;
41std::optional<MuonML::SegmentNodeFeatureId> nodeFeatureIdFromName(
const std::string& name) {
43 if (name ==
"segmentPositionX_m")
return FeatureId::SegmentPositionX;
44 if (name ==
"segmentPositionY_m")
return FeatureId::SegmentPositionY;
45 if (name ==
"segmentPositionZ_m")
return FeatureId::SegmentPositionZ;
46 if (name ==
"segmentDirectionX")
return FeatureId::SegmentDirectionX;
47 if (name ==
"segmentDirectionY")
return FeatureId::SegmentDirectionY;
48 if (name ==
"segmentDirectionZ")
return FeatureId::SegmentDirectionZ;
49 if (name ==
"bucket_chamberIndex")
return FeatureId::BucketChamberIndex;
50 if (name ==
"bucket_layers")
return FeatureId::BucketLayers;
51 if (name ==
"bucket_sector")
return FeatureId::BucketSector;
52 if (name ==
"bucket_segments")
return FeatureId::BucketSegments;
62 case FeatureId::SegmentPositionX:
return static_cast<float>(
pos.x());
63 case FeatureId::SegmentPositionY:
return static_cast<float>(
pos.y());
64 case FeatureId::SegmentPositionZ:
return static_cast<float>(
pos.z());
65 case FeatureId::SegmentDirectionX:
return static_cast<float>(
dir.x());
66 case FeatureId::SegmentDirectionY:
return static_cast<float>(
dir.y());
67 case FeatureId::SegmentDirectionZ:
return static_cast<float>(
dir.z());
68 case FeatureId::BucketChamberIndex:
return static_cast<float>(bucket.
chamberIndex);
69 case FeatureId::BucketLayers:
return static_cast<float>(bucket.
layers);
70 case FeatureId::BucketSector:
return static_cast<float>(bucket.
sector);
71 case FeatureId::BucketSegments:
return static_cast<float>(bucket.
nSegments);
84 Ort::AllocatorWithDefaultOptions allocator;
85 Ort::ModelMetadata
meta =
model().GetModelMetadata();
86 auto keys =
meta.GetCustomMetadataMapKeysAllocated(allocator);
87 std::vector<std::string> keyList;
88 keyList.reserve(keys.size());
89 for (
const auto& k : keys) keyList.emplace_back(k.get());
91 constexpr std::array<std::string_view, 4> candidates{
92 "x_feature_names",
"node_feature_names",
"feature_names",
"input_feature_names"};
94 std::vector<std::string> names;
95 for (std::string_view key : candidates) {
96 const std::string keyStr{key};
97 if (std::find(keyList.begin(), keyList.end(), keyStr) == keyList.end())
continue;
108 " (tried x_feature_names/node_feature_names/feature_names/input_feature_names)."
109 " Falling back to default training order.");
112 ATH_MSG_ERROR(
"Model metadata key '" << usedKey <<
"' has " << names.size()
114 return StatusCode::FAILURE;
116 for (
const std::string& n : names) {
117 if (!nodeFeatureIdFromName(n).has_value()) {
118 ATH_MSG_ERROR(
"Unsupported node feature name in model metadata ('" << usedKey
119 <<
"'): '" << n <<
"'."
120 " Add mapping in SegmentEdgeClassifierTool::nodeFeatureValue().");
121 return StatusCode::FAILURE;
125 ATH_MSG_DEBUG(
"Using node feature names from model metadata key '" << usedKey <<
"'.");
130 const auto id = nodeFeatureIdFromName(n);
131 if (!
id.has_value()) {
132 ATH_MSG_ERROR(
"Internal feature-id resolution failed for node feature name '" << n <<
"'.");
133 return StatusCode::FAILURE;
138 std::ostringstream order;
139 order <<
"Node feature order:";
150 return StatusCode::FAILURE;
155 return StatusCode::FAILURE;
161 std::ofstream out{
m_debugDumpFile.value(), std::ios::out | std::ios::trunc};
163 ATH_MSG_ERROR(
"Could not create segment-edge debug dump file: "
165 return StatusCode::FAILURE;
168 nlohmann::ordered_json metadata;
169 metadata[
"record_type"] =
"metadata";
170 metadata[
"format_version"] = 1;
171 metadata[
"tool"] =
"SegmentEdgeClassifierTool";
177 metadata[
"edge_attr_feature_names"] = {
178 "deltaPositionX_m",
"deltaPositionY_m",
"deltaPositionZ_m",
179 "distance_m",
"cos_opening_angle",
"same_chamber",
"same_sector"};
180 metadata[
"edge_index_layout"] =
"row_major_2_by_E";
181 metadata[
"edge_order"] =
"directed src_to_dst; row 0 then row 1";
186 out << metadata.dump() <<
'\n';
190 <<
" (DebugDumpMaxEvents="
194 return StatusCode::SUCCESS;
198 ATH_MSG_ERROR(
"runGraphInference is not supported by SegmentEdgeClassifierTool. Use SegmentEdgeInferenceAlg + ISegmentEdgeClassifierTool methods.");
199 return StatusCode::FAILURE;
208 std::vector<Amg::Vector3D> pos, dir;
209 std::vector<BucketSegmentFeatures> bucket;
212 std::map<SegmentGroupKey, int> segmentMultiplicity{};
215 ++segmentMultiplicity[segmentGroupKey(*seg)];
223 const int chamberIdx =
static_cast<int>(seg->
chamberIndex());
224 const int layers = segmentLayerCount(*seg);
225 const int sec = seg->
sector();
226 const auto multIt = segmentMultiplicity.find(segmentGroupKey(*seg));
227 const int nSeg = (multIt != segmentMultiplicity.end()) ? multIt->second : 1;
230 pos.emplace_back(p.x() / Gaudi::Units::m,
231 p.y() / Gaudi::Units::m,
232 p.z() / Gaudi::Units::m);
233 dir.emplace_back(d.x(), d.y(), d.z());
236 graph.
nodeFeatures.push_back(nodeFeatureValue(featureId, pos.back(), dir.back(), bucket.back()));
242 if (pos.size() != graph.
nNodes || dir.size() != graph.
nNodes || bucket.size() != graph.
nNodes) {
244 <<
", pos=" << pos.size() <<
", dir=" << dir.size() <<
", bucket=" << bucket.size());
245 return StatusCode::FAILURE;
250 return StatusCode::SUCCESS;
253 std::unordered_map<int, std::vector<std::size_t>> nodesBySector;
254 nodesBySector.reserve(graph.
nNodes);
255 for (std::size_t i = 0; i < graph.
nNodes; ++i) {
256 nodesBySector[bucket[i].sector].push_back(i);
259 auto normalizeSector = [&](
int s) {
268 const std::size_t maxEdges = graph.
nNodes * (graph.
nNodes - 1);
272 for (std::size_t i = 0; i < graph.
nNodes; ++i) {
273 std::unordered_set<int> targetSectors;
276 targetSectors.insert(normalizeSector(bucket[i].sector + delta));
279 for (
const int sec : targetSectors) {
280 auto it = nodesBySector.find(sec);
281 if (it == nodesBySector.end())
continue;
282 for (
const std::size_t j : it->second) {
283 if (i == j)
continue;
285 const float cosang =
static_cast<float>(dir[i].dot(dir[j]));
288 graph.
edgeIndex.push_back(
static_cast<int64_t
>(i));
289 graph.
edgeIndex.push_back(
static_cast<int64_t
>(j));
292 const float dx =
static_cast<float>(delta.x());
293 const float dy =
static_cast<float>(delta.y());
294 const float dz =
static_cast<float>(delta.z());
295 const float dist =
static_cast<float>(delta.mag());
296 graph.
edgeFeatures.insert(graph.
edgeFeatures.end(), {dx,dy,dz,dist,cosang, float(bucket[i].chamberIndex==bucket[j].chamberIndex), float(bucket[i].sector==bucket[j].sector)});
302 <<
", kept nodes=" << graph.
nNodes
303 <<
", built edges=" << graph.
nEdges);
304 return StatusCode::SUCCESS;
309 std::vector<SegmentEdgeScore>& scores)
const {
311 if (!graph.
nNodes)
return StatusCode::SUCCESS;
314 return StatusCode::SUCCESS;
320 return StatusCode::FAILURE;
324 <<
"; expected " << (2 * graph.
nEdges));
325 return StatusCode::FAILURE;
330 return StatusCode::FAILURE;
334 raw.
graph = std::make_unique<InferenceGraph>();
339 for (std::size_t e = 0; e < graph.
nEdges; ++e) {
346 Ort::MemoryInfo memInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
348 const std::vector<int64_t> nodeShape{
static_cast<int64_t
>(graph.
nNodes),
static_cast<int64_t
>(
kNodeFeatureCount)};
349 raw.
graph->dataTensor.emplace_back(
350 Ort::Value::CreateTensor<float>(memInfo,
356 const std::vector<int64_t> edgeIndexShape{2,
static_cast<int64_t
>(graph.
nEdges)};
357 raw.
graph->dataTensor.emplace_back(
358 Ort::Value::CreateTensor<int64_t>(memInfo,
361 edgeIndexShape.data(),
362 edgeIndexShape.size()));
367 const std::vector<int64_t> edgeAttrShape{
static_cast<int64_t
>(graph.
nEdges),
static_cast<int64_t
>(
kEdgeFeatureCount)};
368 raw.
graph->dataTensor.emplace_back(
369 Ort::Value::CreateTensor<float>(memInfo,
372 edgeAttrShape.data(),
373 edgeAttrShape.size()));
375 const std::vector<const char*> inputNames{
379 const std::vector<const char*> outputNames{
m_outputName.value().c_str()};
380 ATH_MSG_DEBUG(
"classifyEdges: ONNX inputs shapes x=[" << nodeShape[0] <<
"," << nodeShape[1]
381 <<
"], edge_index=[" << edgeIndexShape[0] <<
"," << edgeIndexShape[1]
382 <<
"], edge_attr=[" << edgeAttrShape[0] <<
"," << edgeAttrShape[1] <<
"]");
385 if (raw.
graph->dataTensor.size() <= inputNames.size()) {
386 ATH_MSG_ERROR(
"Missing ONNX output tensor for segment edge inference");
387 return StatusCode::FAILURE;
390 const Ort::Value& outTensor = raw.
graph->dataTensor[inputNames.size()];
391 const auto outInfo = outTensor.GetTensorTypeAndShapeInfo();
392 const std::vector<int64_t> outShape = outInfo.GetShape();
393 const size_t outSize = outInfo.GetElementCount();
394 if (!outShape.empty()) {
395 ATH_MSG_DEBUG(
"classifyEdges: ONNX output rank=" << outShape.size()
396 <<
", first dim=" << outShape.front()
397 <<
", elements=" << outSize);
399 ATH_MSG_DEBUG(
"classifyEdges: ONNX scalar output, elements=" << outSize);
401 if (outSize < graph.
nEdges) {
402 ATH_MSG_ERROR(
"ONNX logits tensor has " << outSize <<
" entries for " << graph.
nEdges <<
" edges");
403 return StatusCode::FAILURE;
406 const float* logits = outTensor.GetTensorData<
float>();
407 scores.reserve(graph.
nEdges);
408 for (std::size_t e=0; e<graph.
nEdges; ++e) {
409 const float l = logits[e];
410 scores.push_back({std::size_t(graph.
edgeIndex[2 * e]),
417 return StatusCode::SUCCESS;
421 const EventContext& ctx,
423 const std::vector<SegmentEdgeScore>& scores)
const {
430 return StatusCode::SUCCESS;
436 scores.size() != graph.
nEdges) {
437 ATH_MSG_ERROR(
"Cannot write segment-edge debug dump: inconsistent graph/output sizes"
438 <<
" nodes=" << graph.
nNodes
440 <<
" edges=" << graph.
nEdges
441 <<
" edgeIndex=" << graph.
edgeIndex.size()
443 <<
" scores=" << scores.size());
444 return StatusCode::FAILURE;
447 nlohmann::json
x = nlohmann::json::array();
448 x.get_ref<nlohmann::json::array_t&>().reserve(graph.
nodeFeatures.size());
450 x.push_back(std::isfinite(value) ? nlohmann::json(value)
451 : nlohmann::json(
nullptr));
454 nlohmann::json edgeIndex = nlohmann::json::array();
455 edgeIndex.get_ref<nlohmann::json::array_t&>().reserve(graph.
nEdges * 2);
457 for (std::size_t edge = 0; edge < graph.
nEdges; ++edge) {
458 edgeIndex.push_back(graph.
edgeIndex[2 * edge]);
460 for (std::size_t edge = 0; edge < graph.
nEdges; ++edge) {
461 edgeIndex.push_back(graph.
edgeIndex[2 * edge + 1]);
464 nlohmann::json edgeAttr = nlohmann::json::array();
465 edgeAttr.get_ref<nlohmann::json::array_t&>().reserve(graph.
edgeFeatures.size());
467 edgeAttr.push_back(std::isfinite(value) ? nlohmann::json(value)
468 : nlohmann::json(
nullptr));
471 nlohmann::json logits = nlohmann::json::array();
472 nlohmann::json probabilities = nlohmann::json::array();
473 nlohmann::json edgeSrc = nlohmann::json::array();
474 nlohmann::json edgeDst = nlohmann::json::array();
475 logits.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
476 probabilities.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
477 edgeSrc.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
478 edgeDst.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
480 edgeSrc.push_back(score.src);
481 edgeDst.push_back(score.dst);
482 logits.push_back(std::isfinite(score.logit) ? nlohmann::json(score.logit)
483 : nlohmann::json(
nullptr));
484 probabilities.push_back(std::isfinite(score.probability)
485 ? nlohmann::json(score.probability)
486 : nlohmann::json(
nullptr));
489 std::ofstream out{
m_debugDumpFile.value(), std::ios::out | std::ios::app};
491 ATH_MSG_ERROR(
"Could not append to segment-edge debug dump file: "
493 return StatusCode::FAILURE;
496 const unsigned int dumpIndex =
498 nlohmann::ordered_json event;
499 event[
"record_type"] =
"event";
500 event[
"format_version"] = 1;
501 event[
"dump_index"] = dumpIndex;
502 event[
"run_number"] = ctx.eventID().run_number();
503 event[
"lumi_block"] = ctx.eventID().lumi_block();
504 event[
"event_number"] = ctx.eventID().event_number();
505 event[
"slot"] = ctx.slot();
506 event[
"n_nodes"] = graph.
nNodes;
507 event[
"n_edges"] = graph.
nEdges;
509 event[
"edge_index_shape"] = {2, graph.
nEdges};
511 event[
"logits_shape"] = {graph.
nEdges};
512 event[
"x"] = std::move(
x);
513 event[
"edge_index"] = std::move(edgeIndex);
514 event[
"edge_attr"] = std::move(edgeAttr);
515 event[
"edge_src"] = std::move(edgeSrc);
516 event[
"edge_dst"] = std::move(edgeDst);
517 event[
"logits"] = std::move(logits);
518 event[
"probabilities"] = std::move(probabilities);
519 out <<
event.dump() <<
'\n';
524 return StatusCode::SUCCESS;
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_WARNING(x)
virtual void lock()=0
Interface to allow an object to lock itself when made const in SG.
Define macros for attributes used to control the static checker.
#define ATLAS_THREAD_SAFE
size_type size() const noexcept
Returns the number of elements in the collection.
int nTrigEtaLayers() const
Returns the number of trigger eta layers.
int nPrecisionHits() const
Amg::Vector3D direction() const
Returns the direction as Amg::Vector.
::Muon::MuonStationIndex::ChIndex chamberIndex() const
Returns the chamber index.
Amg::Vector3D position() const
Returns the position as Amg::Vector.
int nPhiLayers() const
Returns the number of phi layers.
int etaIndex() const
Returns the eta index, which corresponds to stationEta in the offline identifiers (and the ).
Eigen::Matrix< double, 3, 1 > Vector3D
SegmentNodeFeatureId
Identifier for each node feature in segment-based GNNs.
MuonSegmentContainer_v1 MuonSegmentContainer
Definition of the current "MuonSegment container version".
MuonSegment_v1 MuonSegment
Reference the current persistent version:
Segment features derived from or stored in bucket metadata.
int chamberIndex
Muon chamber index of the segment.
int sector
Sector number (typically 0–15).
int layers
Total number of active layers in the segment.
int nSegments
Count of segments in the same chamber/sector/eta group.
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.
std::vector< float > edgeFeatures
packed [E,7]: dpos(3), dist, cos, same_chamber, same_sector
std::vector< int64_t > edgeIndex
packed edge pairs [src0,dst0,src1,dst1,...]
std::vector< const xAOD::MuonSegment_v1 * > segments
std::vector< float > nodeFeatures
packed [N,10]: pos_m(3), dir_u(3), bucket(4)