ATLAS Offline Software
Loading...
Searching...
No Matches
SegmentEdgeClassifierTool.cxx
Go to the documentation of this file.
2#include "InferenceUtils.h"
10#include "Acts/Utilities/Helpers.hpp"
12#include "GaudiKernel/SystemOfUnits.h"
13#include <nlohmann/json.hpp>
14#include <algorithm>
15#include <array>
16#include <cmath>
17#include <fstream>
18#include <mutex>
19#include <map>
20#include <optional>
21#include <sstream>
22#include <tuple>
23#include <unordered_map>
24#include <unordered_set>
25
26namespace {
27using SegmentGroupKey = std::tuple<int, int, int>; // sector, chamberIndex, etaIndex
28
29SegmentGroupKey segmentGroupKey(const xAOD::MuonSegment& seg) {
30 return {seg.sector(), static_cast<int>(seg.chamberIndex()), seg.etaIndex()};
31}
32
36int layersInBucket(const MuonR4::SpacePointBucket& bucket) {
38 std::vector<unsigned int> uniqueLayers;
39 uniqueLayers.reserve(bucket.size());
40 for (const MuonR4::SpacePointBucket::value_type& sp : bucket) {
41 const unsigned int layNum = sorter.sectorLayerNum(*sp);
42 if (!Acts::rangeContainsValue(uniqueLayers, layNum)) {
43 uniqueLayers.push_back(layNum);
44 }
45 }
46 return static_cast<int>(uniqueLayers.size());
47}
48
54inline int sectorDistance(int a, int b, int mod) {
55 int d = std::abs(a - b);
56 return mod > 0 ? std::min(d, mod - d) : d;
57}
58
59std::optional<MuonML::SegmentNodeFeatureId> nodeFeatureIdFromName(const std::string& name) {
60 using FeatureId = MuonML::SegmentNodeFeatureId;
61 if (name == "segmentPositionX_m") return FeatureId::SegmentPositionX;
62 if (name == "segmentPositionY_m") return FeatureId::SegmentPositionY;
63 if (name == "segmentPositionZ_m") return FeatureId::SegmentPositionZ;
64 if (name == "segmentDirectionX") return FeatureId::SegmentDirectionX;
65 if (name == "segmentDirectionY") return FeatureId::SegmentDirectionY;
66 if (name == "segmentDirectionZ") return FeatureId::SegmentDirectionZ;
67 if (name == "bucket_chamberIndex") return FeatureId::BucketChamberIndex;
68 if (name == "bucket_layers") return FeatureId::BucketLayers;
69 if (name == "bucket_sector") return FeatureId::BucketSector;
70 if (name == "bucket_segments") return FeatureId::BucketSegments;
71 return std::nullopt;
72}
73
74float nodeFeatureValue(MuonML::SegmentNodeFeatureId feature,
75 const Amg::Vector3D& pos,
76 const Amg::Vector3D& dir,
77 const MuonML::BucketSegmentFeatures& bucket) {
78 using FeatureId = MuonML::SegmentNodeFeatureId;
79 switch (feature) {
80 case FeatureId::SegmentPositionX: return static_cast<float>(pos.x());
81 case FeatureId::SegmentPositionY: return static_cast<float>(pos.y());
82 case FeatureId::SegmentPositionZ: return static_cast<float>(pos.z());
83 case FeatureId::SegmentDirectionX: return static_cast<float>(dir.x());
84 case FeatureId::SegmentDirectionY: return static_cast<float>(dir.y());
85 case FeatureId::SegmentDirectionZ: return static_cast<float>(dir.z());
86 case FeatureId::BucketChamberIndex: return static_cast<float>(bucket.chamberIndex);
87 case FeatureId::BucketLayers: return static_cast<float>(bucket.layers);
88 case FeatureId::BucketSector: return static_cast<float>(bucket.sector);
89 case FeatureId::BucketSegments: return static_cast<float>(bucket.nSegments);
90 }
91 return 0.f;
92}
93}
94
95namespace MuonML {
96
99
100 // Resolve node feature names from model metadata, matching the ONNX exporter.
101 {
102 Ort::AllocatorWithDefaultOptions allocator;
103 Ort::ModelMetadata meta = model().GetModelMetadata();
104 auto keys = meta.GetCustomMetadataMapKeysAllocated(allocator);
105 std::vector<std::string> keyList;
106 keyList.reserve(keys.size());
107 for (const auto& k : keys) keyList.emplace_back(k.get());
108
109 constexpr std::array<std::string_view, 4> candidates{
110 "x_feature_names", "node_feature_names", "feature_names", "input_feature_names"};
111 std::string usedKey;
112 std::vector<std::string> names;
113 for (std::string_view key : candidates) {
114 const std::string keyStr{key};
115 if (std::find(keyList.begin(), keyList.end(), keyStr) == keyList.end()) continue;
116 names = parseFeatureNames(meta.LookupCustomMetadataMapAllocated(keyStr.c_str(), allocator).get());
117 if (!names.empty()) {
118 usedKey = keyStr;
119 break;
120 }
121 }
122
123 if (names.empty()) {
125 ATH_MSG_WARNING("Model metadata has no usable node feature name key"
126 " (tried x_feature_names/node_feature_names/feature_names/input_feature_names)."
127 " Falling back to default training order.");
128 } else {
129 if (names.size() != kNodeFeatureCount) {
130 ATH_MSG_ERROR("Model metadata key '" << usedKey << "' has " << names.size()
131 << " features, expected " << kNodeFeatureCount);
132 return StatusCode::FAILURE;
133 }
134 for (const std::string& n : names) {
135 if (!nodeFeatureIdFromName(n).has_value()) {
136 ATH_MSG_ERROR("Unsupported node feature name in model metadata ('" << usedKey
137 << "'): '" << n << "'."
138 " Add mapping in SegmentEdgeClassifierTool::nodeFeatureValue().");
139 return StatusCode::FAILURE;
140 }
141 }
142 m_nodeFeatureNames = std::move(names);
143 ATH_MSG_DEBUG("Using node feature names from model metadata key '" << usedKey << "'.");
144 }
145
146 m_nodeFeatureIds.reserve(m_nodeFeatureNames.size());
147 for (const std::string& n : m_nodeFeatureNames) {
148 const auto id = nodeFeatureIdFromName(n);
149 if (!id.has_value()) {
150 ATH_MSG_ERROR("Internal feature-id resolution failed for node feature name '" << n << "'.");
151 return StatusCode::FAILURE;
152 }
153 m_nodeFeatureIds.push_back(*id);
154 }
155
156 std::ostringstream order;
157 order << "Node feature order:";
158 for (std::size_t i = 0; i < m_nodeFeatureNames.size(); ++i) {
159 order << " f" << i << "=" << m_nodeFeatureNames[i];
160 if (i + 1 < m_nodeFeatureNames.size()) order << ",";
161 }
162 ATH_MSG_DEBUG(order.str());
163 }
164
166 ATH_MSG_ERROR("Internal node feature setup has " << m_nodeFeatureNames.size()
167 << " entries, expected " << kNodeFeatureCount);
168 return StatusCode::FAILURE;
169 }
170 if (m_nodeFeatureIds.size() != kNodeFeatureCount) {
171 ATH_MSG_ERROR("Internal node feature id setup has " << m_nodeFeatureIds.size()
172 << " entries, expected " << kNodeFeatureCount);
173 return StatusCode::FAILURE;
174 }
175
176 m_cosMin = std::cos(m_maxDeltaThetaDeg.value() * Gaudi::Units::deg);
177
178 if (!m_debugDumpFile.value().empty()) {
179 std::ofstream out{m_debugDumpFile.value(), std::ios::out | std::ios::trunc};
180 if (!out) {
181 ATH_MSG_ERROR("Could not create segment-edge debug dump file: "
182 << m_debugDumpFile.value());
183 return StatusCode::FAILURE;
184 }
185
186 nlohmann::ordered_json metadata;
187 metadata["record_type"] = "metadata";
188 metadata["format_version"] = 1;
189 metadata["tool"] = "SegmentEdgeClassifierTool";
190 metadata["input_names"] = {m_inputNodeName.value(),
191 m_inputEdgeIndexName.value(),
192 m_inputEdgeAttrName.value()};
193 metadata["output_name"] = m_outputName.value();
194 metadata["x_feature_names"] = m_nodeFeatureNames;
195 metadata["edge_attr_feature_names"] = {
196 "deltaPositionX_m", "deltaPositionY_m", "deltaPositionZ_m",
197 "distance_m", "cos_opening_angle", "same_chamber", "same_sector"};
198 metadata["edge_index_layout"] = "row_major_2_by_E";
199 metadata["edge_order"] = "directed src_to_dst; row 0 then row 1";
200 metadata["max_delta_theta_deg"] = m_maxDeltaThetaDeg.value();
201 metadata["max_delta_sector"] = m_maxDeltaSector.value();
202 metadata["sector_modulo"] = m_sectorModulo.value();
203 metadata["debug_dump_max_events"] = m_debugDumpMaxEvents.value();
204 out << metadata.dump() << '\n';
205
206 ATH_MSG_INFO("Writing segment-edge ONNX debug dump to "
207 << m_debugDumpFile.value()
208 << " (DebugDumpMaxEvents="
209 << m_debugDumpMaxEvents.value() << ")");
210 }
211
212 return StatusCode::SUCCESS;
213}
214
215StatusCode SegmentEdgeClassifierTool::runGraphInference(const EventContext&, GraphRawData&) const {
216 ATH_MSG_ERROR("runGraphInference is not supported by SegmentEdgeClassifierTool. Use SegmentEdgeInferenceAlg + ISegmentEdgeClassifierTool methods.");
217 return StatusCode::FAILURE;
218}
219
220StatusCode SegmentEdgeClassifierTool::buildGraph(const EventContext&, const xAOD::MuonSegmentContainer& segments, SegmentEdgeGraph& graph) const {
221 graph = SegmentEdgeGraph{};
222 graph.nNodes = segments.size();
223 graph.segments.reserve(graph.nNodes);
224 graph.nodeFeatures.reserve(graph.nNodes * kNodeFeatureCount);
225
226 std::vector<Amg::Vector3D> pos, dir;
227 std::vector<BucketSegmentFeatures> bucket;
228 pos.reserve(graph.nNodes); dir.reserve(graph.nNodes); bucket.reserve(graph.nNodes);
229
230 std::map<SegmentGroupKey, int> segmentMultiplicity{};
231 for (const xAOD::MuonSegment* seg : segments) {
232 if (!seg) continue;
233 ++segmentMultiplicity[segmentGroupKey(*seg)];
234 }
235
236 for (const xAOD::MuonSegment* seg : segments) {
237 if (!seg) continue;
238 const Amg::Vector3D p = seg->position();
239 Amg::Vector3D d = seg->direction();
240
241 const int chamberIdx = static_cast<int>(seg->chamberIndex());
242 const int layers = layersInBucket(*MuonR4::detailedSegment(*seg)->parent()->parentBucket());
243 const int sec = seg->sector();
244 const auto multIt = segmentMultiplicity.find(segmentGroupKey(*seg));
245 const int nSeg = (multIt != segmentMultiplicity.end()) ? multIt->second : 1;
246
247 graph.segments.push_back(seg);
248 pos.emplace_back(p.x() / Gaudi::Units::m,
249 p.y() / Gaudi::Units::m,
250 p.z() / Gaudi::Units::m);
251 dir.emplace_back(d.x(), d.y(), d.z());
252 bucket.emplace_back(BucketSegmentFeatures{chamberIdx, layers, sec, nSeg});
253 for (const SegmentNodeFeatureId featureId : m_nodeFeatureIds) {
254 graph.nodeFeatures.push_back(nodeFeatureValue(featureId, pos.back(), dir.back(), bucket.back()));
255 }
256 }
257 graph.nNodes = graph.segments.size();
258
259 // Consistency check: all vectors must have same size
260 if (pos.size() != graph.nNodes || dir.size() != graph.nNodes || bucket.size() != graph.nNodes) {
261 ATH_MSG_ERROR("Inconsistent vector sizes during graph building: nodes=" << graph.nNodes
262 << ", pos=" << pos.size() << ", dir=" << dir.size() << ", bucket=" << bucket.size());
263 return StatusCode::FAILURE;
264 }
265
266 if (graph.nNodes < 2) {
267 graph.nEdges = 0;
268 return StatusCode::SUCCESS;
269 }
270
271 auto normalizeSector = [&](int s) {
272 // m_sectorModulo > 0: wrap sector to [0, modulo); <=0: disable wrapping
273 if (m_sectorModulo.value() > 0) {
274 s %= m_sectorModulo.value();
275 if (s < 0) s += m_sectorModulo.value();
276 }
277 return s;
278 };
279
280 // The lookup key must use the same wrapping as the target sectors below:
281 // ATLAS sectors are 1-based (1..16), so a raw key of 16 can never match a
282 // wrapped target of 0, which silently dropped every edge into sector 16.
283 // The per-pair sectorDistance check below enforces the true circular
284 // distance on the raw sector numbers.
285 std::unordered_map<int, std::vector<std::size_t>> nodesBySector;
286 nodesBySector.reserve(graph.nNodes);
287 for (std::size_t i = 0; i < graph.nNodes; ++i) {
288 nodesBySector[normalizeSector(bucket[i].sector)].push_back(i);
289 }
290
291 const std::size_t maxEdges = graph.nNodes * (graph.nNodes - 1);
292 graph.edgeIndex.reserve(2 * maxEdges);
293 graph.edgeFeatures.reserve(kEdgeFeatureCount * maxEdges);
294
295 for (std::size_t i = 0; i < graph.nNodes; ++i) {
296 std::unordered_set<int> targetSectors;
297 targetSectors.reserve(2 * m_maxDeltaSector.value() + 1);
298 for (int delta = -m_maxDeltaSector.value(); delta <= m_maxDeltaSector.value(); ++delta) {
299 targetSectors.insert(normalizeSector(bucket[i].sector + delta));
300 }
301
302 for (const int sec : targetSectors) {
303 auto it = nodesBySector.find(sec);
304 if (it == nodesBySector.end()) continue;
305 for (const std::size_t j : it->second) {
306 if (i == j) continue;
307 if (sectorDistance(bucket[i].sector, bucket[j].sector, m_sectorModulo.value()) > m_maxDeltaSector.value()) continue;
308 const float cosang = static_cast<float>(dir[i].dot(dir[j]));
309 if (cosang < m_cosMin) continue;
310
311 graph.edgeIndex.push_back(static_cast<int64_t>(i));
312 graph.edgeIndex.push_back(static_cast<int64_t>(j));
313
314 const Amg::Vector3D delta = pos[j] - pos[i];
315 const float dx = static_cast<float>(delta.x());
316 const float dy = static_cast<float>(delta.y());
317 const float dz = static_cast<float>(delta.z());
318 const float dist = static_cast<float>(delta.mag());
319 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)});
320 }
321 }
322 }
323 graph.nEdges = graph.edgeIndex.size() / 2;
324 ATH_MSG_DEBUG("buildGraph: input segments=" << segments.size()
325 << ", kept nodes=" << graph.nNodes
326 << ", built edges=" << graph.nEdges);
327 return StatusCode::SUCCESS;
328}
329
330StatusCode SegmentEdgeClassifierTool::classifyEdges(const EventContext& ctx,
331 const SegmentEdgeGraph& graph,
332 std::vector<SegmentEdgeScore>& scores) const {
333 scores.clear();
334 if (!graph.nNodes) return StatusCode::SUCCESS;
335 if (!graph.nEdges) {
336 ATH_CHECK(dumpDebugEvent(ctx, graph, scores));
337 return StatusCode::SUCCESS;
338 }
339
340 if (graph.nodeFeatures.size() != graph.nNodes * kNodeFeatureCount) {
341 ATH_MSG_ERROR("Unexpected node feature size " << graph.nodeFeatures.size()
342 << "; expected " << (graph.nNodes * kNodeFeatureCount));
343 return StatusCode::FAILURE;
344 }
345 if (graph.edgeIndex.size() != 2 * graph.nEdges) {
346 ATH_MSG_ERROR("Unexpected edge index size " << graph.edgeIndex.size()
347 << "; expected " << (2 * graph.nEdges));
348 return StatusCode::FAILURE;
349 }
350 if (graph.edgeFeatures.size() != graph.nEdges * kEdgeFeatureCount) {
351 ATH_MSG_ERROR("Unexpected edge feature size " << graph.edgeFeatures.size()
352 << "; expected " << (graph.nEdges * kEdgeFeatureCount));
353 return StatusCode::FAILURE;
354 }
355
356 GraphRawData raw{};
357 raw.graph = std::make_unique<InferenceGraph>();
358 raw.featureLeaves = graph.nodeFeatures;
359 raw.edgeIndexPacked.reserve(2 * graph.nEdges);
360 raw.srcEdges.reserve(graph.nEdges);
361 raw.desEdges.reserve(graph.nEdges);
362 for (std::size_t e = 0; e < graph.nEdges; ++e) {
363 raw.srcEdges.push_back(graph.edgeIndex[2 * e]);
364 raw.desEdges.push_back(graph.edgeIndex[2 * e + 1]);
365 }
366 raw.edgeIndexPacked.insert(raw.edgeIndexPacked.end(), raw.srcEdges.begin(), raw.srcEdges.end());
367 raw.edgeIndexPacked.insert(raw.edgeIndexPacked.end(), raw.desEdges.begin(), raw.desEdges.end());
368
369 Ort::MemoryInfo memInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
370
371 const std::vector<int64_t> nodeShape{static_cast<int64_t>(graph.nNodes), static_cast<int64_t>(kNodeFeatureCount)};
372 raw.graph->dataTensor.emplace_back(
373 Ort::Value::CreateTensor<float>(memInfo,
374 raw.featureLeaves.data(),
375 raw.featureLeaves.size(),
376 nodeShape.data(),
377 nodeShape.size()));
378
379 const std::vector<int64_t> edgeIndexShape{2, static_cast<int64_t>(graph.nEdges)};
380 raw.graph->dataTensor.emplace_back(
381 Ort::Value::CreateTensor<int64_t>(memInfo,
382 raw.edgeIndexPacked.data(),
383 raw.edgeIndexPacked.size(),
384 edgeIndexShape.data(),
385 edgeIndexShape.size()));
386
387 // ONNX Runtime's CreateTensor API takes a non-const pointer, but it does not
388 // mutate input buffers during inference. Avoid copying edge_attr every event.
389 ATLAS_THREAD_SAFE float* edgeFeaturesData = const_cast<float*>(graph.edgeFeatures.data());
390 const std::vector<int64_t> edgeAttrShape{static_cast<int64_t>(graph.nEdges), static_cast<int64_t>(kEdgeFeatureCount)};
391 raw.graph->dataTensor.emplace_back(
392 Ort::Value::CreateTensor<float>(memInfo,
393 edgeFeaturesData,
394 graph.edgeFeatures.size(),
395 edgeAttrShape.data(),
396 edgeAttrShape.size()));
397
398 const std::vector<const char*> inputNames{
399 m_inputNodeName.value().c_str(),
400 m_inputEdgeIndexName.value().c_str(),
401 m_inputEdgeAttrName.value().c_str()};
402 const std::vector<const char*> outputNames{m_outputName.value().c_str()};
403 ATH_MSG_DEBUG("classifyEdges: ONNX inputs shapes x=[" << nodeShape[0] << "," << nodeShape[1]
404 << "], edge_index=[" << edgeIndexShape[0] << "," << edgeIndexShape[1]
405 << "], edge_attr=[" << edgeAttrShape[0] << "," << edgeAttrShape[1] << "]");
406 ATH_CHECK(runNamedInference(raw, inputNames, outputNames));
407
408 if (raw.graph->dataTensor.size() <= inputNames.size()) {
409 ATH_MSG_ERROR("Missing ONNX output tensor for segment edge inference");
410 return StatusCode::FAILURE;
411 }
412
413 const Ort::Value& outTensor = raw.graph->dataTensor[inputNames.size()];
414 const auto outInfo = outTensor.GetTensorTypeAndShapeInfo();
415 const std::vector<int64_t> outShape = outInfo.GetShape();
416 const size_t outSize = outInfo.GetElementCount();
417 if (!outShape.empty()) {
418 ATH_MSG_DEBUG("classifyEdges: ONNX output rank=" << outShape.size()
419 << ", first dim=" << outShape.front()
420 << ", elements=" << outSize);
421 } else {
422 ATH_MSG_DEBUG("classifyEdges: ONNX scalar output, elements=" << outSize);
423 }
424 if (outSize < graph.nEdges) {
425 ATH_MSG_ERROR("ONNX logits tensor has " << outSize << " entries for " << graph.nEdges << " edges");
426 return StatusCode::FAILURE;
427 }
428
429 const float* logits = outTensor.GetTensorData<float>();
430 scores.reserve(graph.nEdges);
431 for (std::size_t e=0; e<graph.nEdges; ++e) {
432 const float l = logits[e];
433 scores.push_back({std::size_t(graph.edgeIndex[2 * e]),
434 std::size_t(graph.edgeIndex[2 * e + 1]),
435 l,
437 }
438
439 ATH_CHECK(dumpDebugEvent(ctx, graph, scores));
440 return StatusCode::SUCCESS;
441}
442
444 const EventContext& ctx,
445 const SegmentEdgeGraph& graph,
446 const std::vector<SegmentEdgeScore>& scores) const {
447 if (m_debugDumpFile.value().empty()) return StatusCode::SUCCESS;
448
449 std::lock_guard<std::mutex> lock{m_debugDumpMutex};
450 if (m_debugDumpMaxEvents.value() != 0 &&
451 m_debugDumpEvents.load(std::memory_order_relaxed) >=
452 m_debugDumpMaxEvents.value()) {
453 return StatusCode::SUCCESS;
454 }
455
456 if (graph.nodeFeatures.size() != graph.nNodes * kNodeFeatureCount ||
457 graph.edgeIndex.size() != graph.nEdges * 2 ||
458 graph.edgeFeatures.size() != graph.nEdges * kEdgeFeatureCount ||
459 scores.size() != graph.nEdges) {
460 ATH_MSG_ERROR("Cannot write segment-edge debug dump: inconsistent graph/output sizes"
461 << " nodes=" << graph.nNodes
462 << " nodeFeatures=" << graph.nodeFeatures.size()
463 << " edges=" << graph.nEdges
464 << " edgeIndex=" << graph.edgeIndex.size()
465 << " edgeFeatures=" << graph.edgeFeatures.size()
466 << " scores=" << scores.size());
467 return StatusCode::FAILURE;
468 }
469
470 nlohmann::json x = nlohmann::json::array();
471 x.get_ref<nlohmann::json::array_t&>().reserve(graph.nodeFeatures.size());
472 for (const float value : graph.nodeFeatures) {
473 x.push_back(std::isfinite(value) ? nlohmann::json(value)
474 : nlohmann::json(nullptr));
475 }
476
477 nlohmann::json edgeIndex = nlohmann::json::array();
478 edgeIndex.get_ref<nlohmann::json::array_t&>().reserve(graph.nEdges * 2);
479 // This is the actual ONNX [2,E] row-major buffer: all sources then all destinations.
480 for (std::size_t edge = 0; edge < graph.nEdges; ++edge) {
481 edgeIndex.push_back(graph.edgeIndex[2 * edge]);
482 }
483 for (std::size_t edge = 0; edge < graph.nEdges; ++edge) {
484 edgeIndex.push_back(graph.edgeIndex[2 * edge + 1]);
485 }
486
487 nlohmann::json edgeAttr = nlohmann::json::array();
488 edgeAttr.get_ref<nlohmann::json::array_t&>().reserve(graph.edgeFeatures.size());
489 for (const float value : graph.edgeFeatures) {
490 edgeAttr.push_back(std::isfinite(value) ? nlohmann::json(value)
491 : nlohmann::json(nullptr));
492 }
493
494 nlohmann::json logits = nlohmann::json::array();
495 nlohmann::json probabilities = nlohmann::json::array();
496 nlohmann::json edgeSrc = nlohmann::json::array();
497 nlohmann::json edgeDst = nlohmann::json::array();
498 logits.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
499 probabilities.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
500 edgeSrc.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
501 edgeDst.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
502 for (const SegmentEdgeScore& score : scores) {
503 edgeSrc.push_back(score.src);
504 edgeDst.push_back(score.dst);
505 logits.push_back(std::isfinite(score.logit) ? nlohmann::json(score.logit)
506 : nlohmann::json(nullptr));
507 probabilities.push_back(std::isfinite(score.probability)
508 ? nlohmann::json(score.probability)
509 : nlohmann::json(nullptr));
510 }
511
512 std::ofstream out{m_debugDumpFile.value(), std::ios::out | std::ios::app};
513 if (!out) {
514 ATH_MSG_ERROR("Could not append to segment-edge debug dump file: "
515 << m_debugDumpFile.value());
516 return StatusCode::FAILURE;
517 }
518
519 const unsigned int dumpIndex =
520 m_debugDumpEvents.fetch_add(1, std::memory_order_relaxed);
521 nlohmann::ordered_json event;
522 event["record_type"] = "event";
523 event["format_version"] = 1;
524 event["dump_index"] = dumpIndex;
525 event["run_number"] = ctx.eventID().run_number();
526 event["lumi_block"] = ctx.eventID().lumi_block();
527 event["event_number"] = ctx.eventID().event_number();
528 event["slot"] = ctx.slot();
529 event["n_nodes"] = graph.nNodes;
530 event["n_edges"] = graph.nEdges;
531 event["x_shape"] = {graph.nNodes, kNodeFeatureCount};
532 event["edge_index_shape"] = {2, graph.nEdges};
533 event["edge_attr_shape"] = {graph.nEdges, kEdgeFeatureCount};
534 event["logits_shape"] = {graph.nEdges};
535 event["x"] = std::move(x);
536 event["edge_index"] = std::move(edgeIndex);
537 event["edge_attr"] = std::move(edgeAttr);
538 event["edge_src"] = std::move(edgeSrc);
539 event["edge_dst"] = std::move(edgeDst);
540 event["logits"] = std::move(logits);
541 event["probabilities"] = std::move(probabilities);
542 out << event.dump() << '\n';
543
544 ATH_MSG_DEBUG("Wrote segment-edge debug event " << dumpIndex
545 << " to " << m_debugDumpFile.value());
546
547 return StatusCode::SUCCESS;
548}
549
550} // namespace MuonML
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
virtual void lock()=0
Interface to allow an object to lock itself when made const in SG.
static Double_t sp
static Double_t a
#define x
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.
static constexpr std::array< std::string_view, kNodeFeatureCount > kDefaultNodeFeatureNames
static constexpr std::size_t kEdgeFeatureCount
static std::vector< std::string > parseFeatureNames(const std::string &raw)
StatusCode runNamedInference(GraphRawData &graphData, const std::vector< const char * > &inputNames, const std::vector< const char * > &outputNames) const
Generic named inference, for tools with different I/O conventions.
static constexpr std::size_t kNodeFeatureCount
Gaudi::Property< unsigned int > m_debugDumpMaxEvents
Gaudi::Property< std::string > m_outputName
std::atomic< unsigned int > m_debugDumpEvents
Gaudi::Property< std::string > m_inputEdgeAttrName
StatusCode runGraphInference(const EventContext &ctx, GraphRawData &graphData) const override
Not supported by this tool; returns FAILURE.
StatusCode classifyEdges(const EventContext &ctx, const SegmentEdgeGraph &graph, std::vector< SegmentEdgeScore > &scores) const override
Run ONNX inference on graph and populate scores with logit and probability for each edge; called afte...
Gaudi::Property< std::string > m_debugDumpFile
StatusCode buildGraph(const EventContext &ctx, const xAOD::MuonSegmentContainer &segments, SegmentEdgeGraph &graph) const override
Build a GNN graph from segments, computing node and edge features and storing the graph structure in ...
std::vector< std::string > m_nodeFeatureNames
Node feature order expected by the model metadata (resolved at initialize).
Gaudi::Property< std::string > m_inputEdgeIndexName
Gaudi::Property< std::string > m_inputNodeName
std::vector< SegmentNodeFeatureId > m_nodeFeatureIds
StatusCode initialize() override
Retrieve the ONNX model and resolve node feature ordering from metadata.
StatusCode dumpDebugEvent(const EventContext &ctx, const SegmentEdgeGraph &graph, const std::vector< SegmentEdgeScore > &scores) const
: The muon space point bucket represents a collection of points that will bre processed together in t...
The SpacePointPerLayerSorter sort two given space points by their layer Identifier.
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 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.
Definition MuonMLEvent.h:28
const Segment * detailedSegment(const xAOD::MuonSegment &seg)
Helper function to navigate from the xAOD::MuonSegment to the MuonR4::Segment.
-diff
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.
Definition GraphData.h:25
FeatureVec_t featureLeaves
Vector containing all features.
Definition GraphData.h:30
EdgeCounterVec_t edgeIndexPacked
Packed edge index buffer (kept alive for ONNX tensors that reference it) This stores [srcEdges,...
Definition GraphData.h:42
std::unique_ptr< InferenceGraph > graph
Pointer to the graph to be parsed to ONNX.
Definition GraphData.h:46
EdgeCounterVec_t srcEdges
Vector encoding the source index of the.
Definition GraphData.h:32
EdgeCounterVec_t desEdges
Vect.
Definition GraphData.h:34
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)