ATLAS Offline Software
Loading...
Searching...
No Matches
MuonML::SegmentEdgeClassifierTool Class Referencefinal

Runs a segment-level GNN on reconstructed muon segments to classify segment-pair edges as "good" or "background". More...

#include <SegmentEdgeClassifierTool.h>

Inheritance diagram for MuonML::SegmentEdgeClassifierTool:
Collaboration diagram for MuonML::SegmentEdgeClassifierTool:

Public Member Functions

StatusCode initialize () override
 Retrieve the ONNX model and resolve node feature ordering from metadata.
StatusCode runGraphInference (const EventContext &ctx, GraphRawData &graphData) const override
 Not supported by this tool; returns FAILURE.
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 graph.
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 after buildGraph().
StatusCode buildGraph (const EventContext &ctx, GraphRawData &graphData) const
 GNN-style graph builder (features + edges). Kept for tools that want it.
StatusCode runInference (GraphRawData &graphData) const
 Default ONNX run for GNN case: inputs {"features","edge_index"} -> outputs {"logits"}.
 DeclareInterfaceID (ISegmentEdgeClassifierTool, 1, 0)

Protected Member Functions

StatusCode setupModel ()
Ort::Session & model () const
StatusCode buildFeaturesOnly (const EventContext &ctx, GraphRawData &graphData) const
 Build only features (N,6); attaches one tensor in graph.dataTensor[0].
StatusCode buildTransformerInputs (const EventContext &ctx, GraphRawData &graphData) const
 Build Transformer inputs: features [1,S,6] and pad_mask [1,S] (False = valid), as tensors 0 and 1.
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 Protected Member Functions

static std::string trimFeatureToken (std::string s)
static std::vector< std::string > parseFeatureNames (const std::string &raw)

Protected Attributes

SG::ReadHandleKey< MuonR4::SpacePointContainerm_readKey {this, "ReadSpacePoints", "MuonSpacePoints"}
ActsTrk::GeoContextReadKey_t m_geoCtxKey {this, "AlignmentKey", "ActsAlignment", "cond handle key"}
Gaudi::Property< int > m_minLayers {this, "MinLayersValid", 3}
Gaudi::Property< int > m_maxChamberDelta {this, "MaxChamberDelta", 13}
Gaudi::Property< int > m_maxSectorDelta {this, "MaxSectorDelta", 1}
Gaudi::Property< double > m_maxDistXY {this, "MaxDistXY", 6800.0}
Gaudi::Property< double > m_maxAbsDz {this, "MaxAbsDz", 15000.0}
Gaudi::Property< unsigned int > m_debugDumpFirstNNodes {this, "DebugDumpFirstNNodes", 5}
Gaudi::Property< unsigned int > m_debugDumpFirstNEdges {this, "DebugDumpFirstNEdges", 12}
Gaudi::Property< bool > m_validateEdges {this, "ValidateEdges", true}
Gaudi::Property< bool > m_sanitizeNonFinitePredictions
bool m_isCuda {false}
int m_cudaDeviceId {0}

Static Protected Attributes

static constexpr std::size_t kBucketFeatureCount = 6
static constexpr std::size_t kNodeFeatureCount = 10
static constexpr std::size_t kEdgeFeatureCount = 7
static constexpr std::array< std::string_view, kNodeFeatureCountkDefaultNodeFeatureNames

Private Member Functions

StatusCode dumpDebugEvent (const EventContext &ctx, const SegmentEdgeGraph &graph, const std::vector< SegmentEdgeScore > &scores) const

Private Attributes

Gaudi::Property< float > m_maxDeltaThetaDeg {this, "MaxDeltaThetaDeg", 35.f}
Gaudi::Property< int > m_maxDeltaSector {this, "MaxDeltaSector", 1}
Gaudi::Property< int > m_sectorModulo {this, "SectorModulo", 16}
Gaudi::Property< std::string > m_inputNodeName {this, "InputNodeName", "x"}
Gaudi::Property< std::string > m_inputEdgeIndexName {this, "InputEdgeIndexName", "edge_index"}
Gaudi::Property< std::string > m_inputEdgeAttrName {this, "InputEdgeAttrName", "edge_attr"}
Gaudi::Property< std::string > m_outputName {this, "OutputName", "logits"}
Gaudi::Property< std::string > m_debugDumpFile {this, "DebugDumpFile", ""}
Gaudi::Property< unsigned int > m_debugDumpMaxEvents {this, "DebugDumpMaxEvents", 0}
float m_cosMin {0.f}
std::vector< std::string > m_nodeFeatureNames {}
 Node feature order expected by the model metadata (resolved at initialize).
std::vector< SegmentNodeFeatureIdm_nodeFeatureIds {}
std::mutex m_debugDumpMutex
std::atomic< unsigned int > m_debugDumpEvents {0}
ToolHandle< AthOnnx::IOnnxRuntimeSessionToolm_onnxSessionTool

Detailed Description

Runs a segment-level GNN on reconstructed muon segments to classify segment-pair edges as "good" or "background".

The tool reads a xAOD::MuonSegmentContainer and builds a graph where:

  • Nodes are muon segments, each with 10 features:
    • Position and direction (6 floats)
    • Chamber index, layer count, sector, and segment multiplicity (4 floats)
  • Edges connect all segment pairs within an angular threshold (cos(angle) >= cos(MaxDeltaThetaDeg)) and sector distance, with 7 features:
    • Spatial displacement (3 floats: dx, dy, dz)
    • Distance magnitude (1 float)
    • Angle (dot product, 1 float)
    • Chamber and sector match flags (2 flags)

The tool then runs an ONNX model (typically a GIN or GCN variant) to produce a logit or probability for each edge, enabling downstream algorithms to filter low-quality segment associations and improve reconstruction efficiency.

Key difference from GraphBucketFilterTool: operates at segment (edge) level rather than bucket (node) level, and the interface uses discrete graph structures (SegmentEdgeGraph) rather than tensors for input/output.

Note: runGraphInference() is not supported by this tool; use SegmentEdgeInferenceAlg and the ISegmentEdgeClassifierTool methods instead.

Definition at line 61 of file SegmentEdgeClassifierTool.h.

Member Function Documentation

◆ buildFeaturesOnly()

StatusCode BucketInferenceToolBase::buildFeaturesOnly ( const EventContext & ctx,
GraphRawData & graphData ) const
protectedinherited

Build only features (N,6); attaches one tensor in graph.dataTensor[0].

Definition at line 87 of file BucketInferenceToolBase.cxx.

88 {
89
90 graphData.graph.reset();
91 graphData.srcEdges.clear();
92 graphData.desEdges.clear();
93 graphData.edgeIndexPacked.clear();
94 graphData.featureLeaves.clear();
95 graphData.spacePointsInBucket.clear();
96 graphData.graph = std::make_unique<InferenceGraph>();
97 graphData.graph->dataTensor.reserve(1); // features input; outputs are reserved in runNamedInference()
98
99 const MuonR4::SpacePointContainer* buckets{nullptr};
100 ATH_CHECK(SG::get(buckets, m_readKey, ctx));
101
102 const ActsTrk::GeometryContext* gctx = nullptr;
103 ATH_CHECK(SG::get(gctx, m_geoCtxKey, ctx));
104
105 std::vector<BucketGraphUtils::NodeAux> nodes;
106 BucketGraphUtils::buildNodesAndFeatures(*buckets, *gctx, nodes,
107 graphData.featureLeaves,
108 graphData.spacePointsInBucket); // now int64_t-compatible
109
110 const int64_t numNodes = static_cast<int64_t>(nodes.size());
111 ATH_MSG_DEBUG("Total buckets: " << buckets->size()
112 << " -> nodes (size>0): " << numNodes
113 << " | features.size()=" << graphData.featureLeaves.size());
114
115 if (numNodes == 0) {
116 ATH_MSG_WARNING("No valid buckets found (all have size 0.0). Skipping inference.");
117 return StatusCode::SUCCESS;
118 }
119
120 const int64_t nFeatPerNode = static_cast<int64_t>(kBucketFeatureCount);
121 if (numNodes * nFeatPerNode != static_cast<int64_t>(graphData.featureLeaves.size())) {
122 ATH_MSG_ERROR( "Feature size mismatch: expected " << (numNodes * nFeatPerNode)
123 << " got " << graphData.featureLeaves.size());
124 return StatusCode::FAILURE;
125 }
126
127 Ort::MemoryInfo memInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
128 std::vector<int64_t> featShape{numNodes, nFeatPerNode};
129 graphData.graph->dataTensor.emplace_back(
130 Ort::Value::CreateTensor<float>(memInfo,
131 graphData.featureLeaves.data(),
132 graphData.featureLeaves.size(),
133 featShape.data(),
134 featShape.size()));
135 return StatusCode::SUCCESS;
136}
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
size_type size() const noexcept
Returns the number of elements in the collection.
ActsTrk::GeoContextReadKey_t m_geoCtxKey
static constexpr std::size_t kBucketFeatureCount
SG::ReadHandleKey< MuonR4::SpacePointContainer > m_readKey
void buildNodesAndFeatures(const MuonR4::SpacePointContainer &buckets, const ActsTrk::GeometryContext &gctx, std::vector< NodeAux > &nodes, std::vector< float > &featuresLeaves, std::vector< int64_t > &spInBucket)
Build nodes + flat features (N,6) and number of SPs per kept bucket.
DataVector< SpacePointBucket > SpacePointContainer
Abrivation of the space point container type.
const T * get(const ReadCondHandleKey< T > &key, const EventContext &ctx)
Convenience function to retrieve an object given a ReadCondHandleKey.
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
NodeConnectVec_t spacePointsInBucket
Vector keeping track of how many space points are in each parsed bucket.
Definition GraphData.h:36

◆ buildGraph() [1/2]

StatusCode BucketInferenceToolBase::buildGraph ( const EventContext & ctx,
GraphRawData & graphData ) const
inherited

GNN-style graph builder (features + edges). Kept for tools that want it.

Definition at line 200 of file BucketInferenceToolBase.cxx.

201 {
202
203 graphData.graph.reset();
204 graphData.srcEdges.clear();
205 graphData.desEdges.clear();
206 graphData.featureLeaves.clear();
207 graphData.spacePointsInBucket.clear();
208 graphData.edgeIndexPacked.clear();
209 graphData.graph = std::make_unique<InferenceGraph>();
210 graphData.graph->dataTensor.reserve(2); // features and edge_index inputs; outputs are reserved in runNamedInference()
211
212 const MuonR4::SpacePointContainer* buckets{nullptr};
213 ATH_CHECK(SG::get(buckets, m_readKey, ctx));
214
215 const ActsTrk::GeometryContext* gctx = nullptr;
216 ATH_CHECK(SG::get(gctx, m_geoCtxKey, ctx));
217
218 std::vector<BucketGraphUtils::NodeAux> nodes;
219
220 BucketGraphUtils::buildNodesAndFeatures(*buckets, *gctx, nodes,
221 graphData.featureLeaves,
222 graphData.spacePointsInBucket);
223
224 const int64_t numNodes = static_cast<int64_t>(nodes.size());
225 ATH_MSG_DEBUG("Total buckets: " << buckets->size()
226 << " -> nodes (size>0): " << numNodes
227 << " | features.size()=" << graphData.featureLeaves.size());
228
229 if (numNodes == 0) {
230 ATH_MSG_WARNING("No valid buckets found (all have size 0.0). Skipping graph building.");
231 return StatusCode::SUCCESS;
232 }
233
234 const int64_t nFeatPerNode = static_cast<int64_t>(kBucketFeatureCount);
235 if (numNodes * nFeatPerNode != static_cast<int64_t>(graphData.featureLeaves.size())) {
236 ATH_MSG_ERROR("Feature size mismatch: expected " << (numNodes * nFeatPerNode)
237 << " got " << graphData.featureLeaves.size());
238 return StatusCode::FAILURE;
239 }
240
241 Ort::MemoryInfo memInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
242 std::vector<int64_t> featShape{numNodes, nFeatPerNode};
243 graphData.graph->dataTensor.emplace_back(
244 Ort::Value::CreateTensor<float>(memInfo,
245 graphData.featureLeaves.data(),
246 graphData.featureLeaves.size(),
247 featShape.data(),
248 featShape.size()));
249
256 graphData.srcEdges, graphData.desEdges);
257 if (m_validateEdges) {
258 size_t bad = 0;
259 size_t write = 0;
260 for (size_t k = 0; k < graphData.srcEdges.size(); ++k) {
261 const int64_t u = graphData.srcEdges[k];
262 const int64_t v = graphData.desEdges[k];
263 const bool okU = (u >= 0 && u < numNodes);
264 const bool okV = (v >= 0 && v < numNodes);
265 if (okU && okV) {
266 graphData.srcEdges[write] = u;
267 graphData.desEdges[write] = v;
268 ++write;
269 } else {
270 ++bad;
271 ATH_MSG_DEBUG( "Drop invalid edge " << k << ": (" << u << "->" << v
272 << "), valid node range [0," << (numNodes-1) << "]");
273 }
274 }
275 if (bad) {
276 ATH_MSG_WARNING( "Removed " << bad << " invalid edges out of "
277 << graphData.srcEdges.size());
278 graphData.srcEdges.resize(write);
279 graphData.desEdges.resize(write);
280 }
281 }
282
283 const size_t E = graphData.srcEdges.size();
284
285 if (msgLvl(MSG::DEBUG)) {
286 // DEBUG: Count connections per node
287 ATH_MSG_DEBUG("Edges built: " << E);
288 const size_t dumpE = std::min<std::size_t>(m_debugDumpFirstNEdges.value(), E);
289 for (size_t k = 0; k < dumpE; ++k) {
290 ATH_MSG_DEBUG("EDGE[" << k << "]: "
291 << graphData.srcEdges[k] << " -> "
292 << graphData.desEdges[k]);
293 }
294
295 std::vector<int> nodeConnections(numNodes, 0);
296 for (size_t k = 0; k < graphData.srcEdges.size(); ++k) {
297 const int64_t u = graphData.srcEdges[k];
298 const int64_t v = graphData.desEdges[k];
299 if (u >= 0 && u < numNodes) nodeConnections[u]++;
300 if (v >= 0 && v < numNodes) nodeConnections[v]++;
301 }
302
303 ATH_MSG_DEBUG("=== DEBUGGING: Node Connections (first 10 nodes) ===");
304 const int64_t debugNodeCount = std::min(numNodes, static_cast<int64_t>(10));
305 for (int64_t i = 0; i < debugNodeCount; ++i) {
306 ATH_MSG_DEBUG("Node[" << i << "] connections: " << nodeConnections[i]);
307 }
308 ATH_MSG_DEBUG("=== END DEBUG NODE CONNECTIONS ===");
309
310 ATH_MSG_DEBUG("=== DEBUGGING: Detailed Edge Connections (first 10 nodes) ===");
311 for (int64_t nodeIdx = 0; nodeIdx < debugNodeCount; ++nodeIdx) {
312 std::stringstream connections;
313 connections << "Node[" << nodeIdx << "] connected to: ";
314 bool foundAny = false;
315
316 for (size_t k = 0; k < graphData.srcEdges.size(); ++k) {
317 const int64_t u = graphData.srcEdges[k];
318 const int64_t v = graphData.desEdges[k];
319
320 if (u == nodeIdx) {
321 if (foundAny) connections << ", ";
322 connections << v;
323 foundAny = true;
324 } else if (v == nodeIdx) {
325 if (foundAny) connections << ", ";
326 connections << u;
327 foundAny = true;
328 }
329 }
330
331 if (!foundAny) connections << "none";
332 ATH_MSG_DEBUG(connections.str());
333 }
334 ATH_MSG_DEBUG("=== END DEBUG DETAILED CONNECTIONS ===");
335 }
336
337 nodes = {};
338
339 graphData.edgeIndexPacked.clear();
340 const size_t Efinal = BucketGraphUtils::packEdgeIndex(graphData.srcEdges,
341 graphData.desEdges,
342 graphData.edgeIndexPacked);
343
344 graphData.srcEdges.clear();
345 graphData.desEdges.clear();
346
347 std::vector<int64_t> edgeShape{2, static_cast<int64_t>(Efinal)};
348 graphData.graph->dataTensor.emplace_back(
349 Ort::Value::CreateTensor<int64_t>(memInfo,
350 graphData.edgeIndexPacked.data(),
351 graphData.edgeIndexPacked.size(),
352 edgeShape.data(),
353 edgeShape.size()));
354
355 ATH_MSG_DEBUG("Built sparse bucket graph: N=" << numNodes << ", E=" << Efinal);
356 return StatusCode::SUCCESS;
357}
Gaudi::Property< unsigned int > m_debugDumpFirstNEdges
Gaudi::Property< double > m_maxDistXY
void buildSparseEdges(const std::vector< NodeAux > &nodes, int minLayers, int maxChamberDelta, int maxSectorDelta, double maxDistXY, double maxAbsDz, std::vector< int64_t > &srcEdges, std::vector< int64_t > &dstEdges)
size_t packEdgeIndex(const std::vector< int64_t > &srcEdges, const std::vector< int64_t > &dstEdges, std::vector< int64_t > &edgeIndexPacked)
@ u
Enums for curvilinear frames.
Definition ParamDefs.h:77

◆ buildGraph() [2/2]

StatusCode MuonML::SegmentEdgeClassifierTool::buildGraph ( const EventContext & ctx,
const xAOD::MuonSegmentContainer & segments,
SegmentEdgeGraph & graph ) const
overridevirtual

Build a GNN graph from segments, computing node and edge features and storing the graph structure in graph.

Implements MuonML::ISegmentEdgeClassifierTool.

Definition at line 220 of file SegmentEdgeClassifierTool.cxx.

220 {
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}
static constexpr std::size_t kEdgeFeatureCount
static constexpr std::size_t kNodeFeatureCount
std::vector< SegmentNodeFeatureId > m_nodeFeatureIds
const SpacePointBucket * parentBucket() const
Returns the bucket out of which the seed was formed.
const SegmentSeed * parent() const
Returns the seed out of which the segment was built.
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.
float j(const xAOD::IParticle &, const xAOD::TrackMeasurementValidation &hit, const Eigen::Matrix3d &jab_inv)
MuonSegment_v1 MuonSegment
Reference the current persistent version:

◆ buildTransformerInputs()

StatusCode BucketInferenceToolBase::buildTransformerInputs ( const EventContext & ctx,
GraphRawData & graphData ) const
protectedinherited

Build Transformer inputs: features [1,S,6] and pad_mask [1,S] (False = valid), as tensors 0 and 1.

Definition at line 138 of file BucketInferenceToolBase.cxx.

139 {
140 // Start from (N,6)
141 ATH_CHECK(buildFeaturesOnly(ctx, graphData));
142
143 // Copy features flat buffer for lifetime management
144 std::vector<float> featuresFlat = graphData.featureLeaves;
145 const int64_t S = static_cast<int64_t>(featuresFlat.size() / kBucketFeatureCount);
146
147 if (S == 0) {
148 ATH_MSG_WARNING("No valid features for transformer input. Skipping inference.");
149 return StatusCode::SUCCESS;
150 }
151
152 if (msgLvl(MSG::DEBUG)) {
153 // DEBUG: Print transformer input features for first 10 nodes
154 ATH_MSG_DEBUG("=== DEBUGGING: Transformer input features for first 10 nodes ===");
155 const int64_t debugNodes = std::min(S, static_cast<int64_t>(10));
156 for (int64_t nodeIdx = 0; nodeIdx < debugNodes; ++nodeIdx) {
157 const int64_t baseIdx = nodeIdx * static_cast<int64_t>(kBucketFeatureCount);
158 ATH_MSG_DEBUG("TransformerNode[" << nodeIdx << "]: "
159 << "x=" << featuresFlat[baseIdx + 0] << ", "
160 << "y=" << featuresFlat[baseIdx + 1] << ", "
161 << "z=" << featuresFlat[baseIdx + 2] << ", "
162 << "layers=" << featuresFlat[baseIdx + 3] << ", "
163 << "nSp=" << featuresFlat[baseIdx + 4] << ", "
164 << "bucketSize=" << featuresFlat[baseIdx + 5]);
165 }
166 ATH_MSG_DEBUG("=== END DEBUG TRANSFORMER FEATURES ===");
167 }
168
169 // Rebuild graph with exactly 2 inputs: features [1,S,6], pad_mask [1,S]
170 graphData.graph.reset();
171 graphData.graph = std::make_unique<InferenceGraph>();
172 graphData.graph->dataTensor.reserve(2); // features and pad_mask inputs; outputs are reserved in runNamedInference()
173
174 Ort::MemoryInfo memInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
175
176 // features: [1,S,6] (backed by graphData.featureLeaves to keep alive)
177 std::vector<int64_t> fShape{1, S, static_cast<int64_t>(kBucketFeatureCount)};
178 graphData.featureLeaves.swap(featuresFlat);
179 graphData.graph->dataTensor.emplace_back(
180 Ort::Value::CreateTensor<float>(memInfo,
181 graphData.featureLeaves.data(),
182 graphData.featureLeaves.size(),
183 fShape.data(),
184 fShape.size()));
185
186 // pad_mask: [1,S] (bool). Create ORT-owned tensor and fill with False (=valid).
187 Ort::AllocatorWithDefaultOptions allocator;
188 std::vector<int64_t> mShape{1, S};
189 Ort::Value padVal = Ort::Value::CreateTensor(allocator,
190 mShape.data(),
191 mShape.size(),
192 ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL);
193 bool* maskPtr = padVal.GetTensorMutableData<bool>();
194 for (int64_t i = 0; i < S; ++i) maskPtr[i] = false;
195 graphData.graph->dataTensor.emplace_back(std::move(padVal));
196
197 return StatusCode::SUCCESS;
198}
StatusCode buildFeaturesOnly(const EventContext &ctx, GraphRawData &graphData) const
Build only features (N,6); attaches one tensor in graph.dataTensor[0].

◆ classifyEdges()

StatusCode MuonML::SegmentEdgeClassifierTool::classifyEdges ( const EventContext & ctx,
const SegmentEdgeGraph & graph,
std::vector< SegmentEdgeScore > & scores ) const
overridevirtual

Run ONNX inference on graph and populate scores with logit and probability for each edge; called after buildGraph().

Implements MuonML::ISegmentEdgeClassifierTool.

Definition at line 330 of file SegmentEdgeClassifierTool.cxx.

332 {
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}
#define ATLAS_THREAD_SAFE
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.
Gaudi::Property< std::string > m_outputName
Gaudi::Property< std::string > m_inputEdgeAttrName
Gaudi::Property< std::string > m_inputEdgeIndexName
Gaudi::Property< std::string > m_inputNodeName
StatusCode dumpDebugEvent(const EventContext &ctx, const SegmentEdgeGraph &graph, const std::vector< SegmentEdgeScore > &scores) const
l
Printing final latex table to .tex output file.

◆ DeclareInterfaceID()

MuonML::ISegmentEdgeClassifierTool::DeclareInterfaceID ( ISegmentEdgeClassifierTool ,
1 ,
0  )
inherited

◆ dumpDebugEvent()

StatusCode MuonML::SegmentEdgeClassifierTool::dumpDebugEvent ( const EventContext & ctx,
const SegmentEdgeGraph & graph,
const std::vector< SegmentEdgeScore > & scores ) const
private

Definition at line 443 of file SegmentEdgeClassifierTool.cxx.

446 {
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}
virtual void lock()=0
Interface to allow an object to lock itself when made const in SG.
#define x
Gaudi::Property< unsigned int > m_debugDumpMaxEvents
std::atomic< unsigned int > m_debugDumpEvents
Gaudi::Property< std::string > m_debugDumpFile
virtual void reserve(size_t sz) override
Change the capacity of all aux data vectors.

◆ initialize()

StatusCode MuonML::SegmentEdgeClassifierTool::initialize ( )
override

Retrieve the ONNX model and resolve node feature ordering from metadata.

Definition at line 97 of file SegmentEdgeClassifierTool.cxx.

97 {
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}
#define ATH_MSG_INFO(x)
static constexpr std::array< std::string_view, kNodeFeatureCount > kDefaultNodeFeatureNames
static std::vector< std::string > parseFeatureNames(const std::string &raw)
std::vector< std::string > m_nodeFeatureNames
Node feature order expected by the model metadata (resolved at initialize).
order
Configure Herwig7.

◆ model()

Ort::Session & BucketInferenceToolBase::model ( ) const
protectedinherited

Definition at line 65 of file BucketInferenceToolBase.cxx.

65 {
66 return m_onnxSessionTool->session();
67}
ToolHandle< AthOnnx::IOnnxRuntimeSessionTool > m_onnxSessionTool

◆ parseFeatureNames()

std::vector< std::string > BucketInferenceToolBase::parseFeatureNames ( const std::string & raw)
staticprotectedinherited

Definition at line 32 of file BucketInferenceToolBase.cxx.

32 {
33 std::vector<std::string> out;
34 const std::string s = trimFeatureToken(raw);
35 if (s.empty()) return out;
36
37 // Preferred exporter format: JSON list of strings.
38 if (!s.empty() && s.front() == '[') {
39 bool inQuote = false;
40 std::string token;
41 for (char c : s) {
42 if (c == '"') {
43 if (inQuote) {
44 if (!token.empty()) out.push_back(token);
45 token.clear();
46 }
47 inQuote = !inQuote;
48 continue;
49 }
50 if (inQuote) token.push_back(c);
51 }
52 if (!out.empty()) return out;
53 }
54
55 // Backward-compatible format: comma-separated.
56 std::istringstream ss(s);
57 std::string tok;
58 while (std::getline(ss, tok, ',')) {
59 tok = trimFeatureToken(tok);
60 if (!tok.empty()) out.push_back(tok);
61 }
62 return out;
63}
static Double_t ss
static std::string trimFeatureToken(std::string s)

◆ runGraphInference()

StatusCode MuonML::SegmentEdgeClassifierTool::runGraphInference ( const EventContext & ctx,
GraphRawData & graphData ) const
override

Not supported by this tool; returns FAILURE.

Use SegmentEdgeInferenceAlg + buildGraph() + classifyEdges() instead.

Definition at line 215 of file SegmentEdgeClassifierTool.cxx.

215 {
216 ATH_MSG_ERROR("runGraphInference is not supported by SegmentEdgeClassifierTool. Use SegmentEdgeInferenceAlg + ISegmentEdgeClassifierTool methods.");
217 return StatusCode::FAILURE;
218}

◆ runInference()

StatusCode BucketInferenceToolBase::runInference ( GraphRawData & graphData) const
inherited

Default ONNX run for GNN case: inputs {"features","edge_index"} -> outputs {"logits"}.

Definition at line 532 of file BucketInferenceToolBase.cxx.

532 {
533 std::vector<const char*> inputNames = {"features", "edge_index"};
534 std::vector<const char*> outputNames = {m_outputName.value().c_str()};
535 return runNamedInference(graphData, inputNames, outputNames);
536}
Gaudi::Property< std::string > m_outputName

◆ runNamedInference()

StatusCode BucketInferenceToolBase::runNamedInference ( GraphRawData & graphData,
const std::vector< const char * > & inputNames,
const std::vector< const char * > & outputNames ) const
protectedinherited

Generic named inference, for tools with different I/O conventions.

Definition at line 359 of file BucketInferenceToolBase.cxx.

363{
364 if (!graphData.graph) {
365 ATH_MSG_ERROR("Graph data is not built.");
366 return StatusCode::FAILURE;
367 }
368 if (graphData.graph->dataTensor.empty()) {
369 ATH_MSG_ERROR("No input tensors prepared for inference.");
370 return StatusCode::FAILURE;
371 }
372
373 // Reserve the final size here from the actual I/O lists instead
374 // of hard-coding assumptions in the graph builders.
375 graphData.graph->dataTensor.reserve(inputNames.size() + outputNames.size());
376 if (graphData.graph->dataTensor.size() < inputNames.size()) {
377 ATH_MSG_ERROR("Prepared " << graphData.graph->dataTensor.size()
378 << " tensors but inference expects " << inputNames.size() << " inputs.");
379 return StatusCode::FAILURE;
380 }
381
382 if (msgLvl(MSG::DEBUG)) {
383 // DEBUG: Print actual input tensor data for features tensor
384
385 ATH_MSG_DEBUG("=== DEBUGGING: ONNX Input tensor data ===");
386 if (!graphData.graph->dataTensor.empty()) {
387 const auto& featureTensor = graphData.graph->dataTensor[0];
388 auto featShape = featureTensor.GetTensorTypeAndShapeInfo().GetShape();
389 ATH_MSG_DEBUG("Features tensor shape: [" << featShape[0]
390 << (featShape.size()>1 ? ("," + std::to_string(featShape[1])) : "")
391 << (featShape.size()>2 ? ("," + std::to_string(featShape[2])) : "") << "]");
392
393 float* featData = const_cast<Ort::Value&>(featureTensor).GetTensorMutableData<float>();
394 const size_t totalElements = featureTensor.GetTensorTypeAndShapeInfo().GetElementCount();
395 ATH_MSG_DEBUG("Features tensor total elements: " << totalElements);
396
397 // Print up to 10 nodes; stride = nFeat from tensor shape
398 const size_t nFeat = (featShape.size() > 1 && featShape[1] > 0) ? static_cast<size_t>(featShape[1]) : 1;
399 const size_t nNodes = totalElements / nFeat;
400 const size_t debugNodes = std::min(nNodes, static_cast<size_t>(10));
401
402 // Try to read feature names from model custom metadata.
403 // Prefer x_feature_names (current exporter), then fall back to legacy keys.
404 std::vector<std::string> featNames;
405 {
406 Ort::AllocatorWithDefaultOptions allocator;
407 Ort::ModelMetadata meta = model().GetModelMetadata();
408 auto keys = meta.GetCustomMetadataMapKeysAllocated(allocator);
409 std::vector<std::string> keyNames;
410 keyNames.reserve(keys.size());
411 for (const auto& k : keys) keyNames.emplace_back(k.get());
412 const std::array<std::string, 4> candidates{
413 "x_feature_names", "node_feature_names", "feature_names", "input_feature_names"};
414 for (const std::string& key : candidates) {
415 if (std::find(keyNames.begin(), keyNames.end(), key) != keyNames.end()) {
416 std::string val = meta.LookupCustomMetadataMapAllocated(key.c_str(), allocator).get();
417 featNames = parseFeatureNames(val);
418 break;
419 }
420 }
421 if (featNames.empty()) {
422 ATH_MSG_DEBUG("No usable feature-name metadata key found in model; using generic fN labels.");
423 }
424 }
425 auto featLabel = [&](size_t f) -> std::string {
426 if (f < featNames.size()) return featNames[f];
427 return "f" + std::to_string(f);
428 };
429
430 // Print legend
431 {
432 std::ostringstream legend;
433 legend << "Node feature legend (" << nFeat << " features):";
434 for (size_t f = 0; f < nFeat; ++f) {
435 legend << " f" << f << "=" << featLabel(f);
436 if (f + 1 < nFeat) legend << ",";
437 }
438 ATH_MSG_DEBUG(legend.str());
439 }
440
441 for (size_t n = 0; n < debugNodes; ++n) {
442 std::ostringstream row;
443 row << "ONNXNode[" << n << "]:";
444 for (size_t f = 0; f < nFeat; ++f) {
445 row << " f" << f << "=" << featData[n * nFeat + f];
446 if (f + 1 < nFeat) row << ",";
447 }
448 ATH_MSG_DEBUG(row.str());
449 }
450 }
451 ATH_MSG_DEBUG("=== END DEBUG ONNX INPUT ===");
452 }
453
454 Ort::RunOptions run_options;
455 run_options.SetRunLogSeverityLevel(ORT_LOGGING_LEVEL_ERROR);
456
457 if (m_isCuda) {
458 // ---- CUDA path: use IoBinding so tensors stay on device ----
459 Ort::IoBinding binding(model());
460 for (std::size_t i = 0; i < inputNames.size(); ++i) {
461 binding.BindInput(inputNames[i], graphData.graph->dataTensor[i]);
462 }
463 // Bind outputs to CPU so predictions are directly readable after sync.
464 Ort::MemoryInfo cpuOut = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
465 for (const char* outName : outputNames) {
466 binding.BindOutput(outName, cpuOut);
467 }
468
469 model().Run(run_options, binding);
470 binding.SynchronizeOutputs();
471
472 std::vector<Ort::Value> outputs = binding.GetOutputValues();
473 if (outputs.empty()) {
474 ATH_MSG_ERROR("IoBinding inference returned empty output.");
475 return StatusCode::FAILURE;
476 }
477
478 float* outData = outputs[0].GetTensorMutableData<float>();
479 const size_t outSize = outputs[0].GetTensorTypeAndShapeInfo().GetElementCount();
480 ATH_MSG_DEBUG("ONNX (IoBinding) raw output elementCount = " << outSize);
481
482 if (m_sanitizeNonFinitePredictions.value()) {
483 std::span<float> preds(outData, outData + outSize);
484 for (size_t i = 0; i < outSize; ++i) {
485 if (!std::isfinite(preds[i])) {
486 ATH_MSG_WARNING("Non-finite prediction detected at " << i << " -> set to -100.");
487 preds[i] = -100.0f;
488 }
489 }
490 }
491
492 for (auto& v : outputs) {
493 graphData.graph->dataTensor.emplace_back(std::move(v));
494 }
495 return StatusCode::SUCCESS;
496 }
497
498 // ---- CPU path ----
499 std::vector<Ort::Value> outputs =
500 model().Run(run_options,
501 inputNames.data(),
502 graphData.graph->dataTensor.data(),
503 inputNames.size(),
504 outputNames.data(),
505 outputNames.size());
506
507 if (outputs.empty()) {
508 ATH_MSG_ERROR("Inference returned empty output.");
509 return StatusCode::FAILURE;
510 }
511
512 float* outData = outputs[0].GetTensorMutableData<float>();
513 const size_t outSize = outputs[0].GetTensorTypeAndShapeInfo().GetElementCount();
514 ATH_MSG_DEBUG("ONNX raw output elementCount = " << outSize);
515
516 if (m_sanitizeNonFinitePredictions.value()) {
517 std::span<float> preds(outData, outData + outSize);
518 for (size_t i = 0; i < outSize; ++i) {
519 if (!std::isfinite(preds[i])) {
520 ATH_MSG_WARNING("Non-finite prediction detected at " << i << " -> set to -100.");
521 preds[i] = -100.0f;
522 }
523 }
524 }
525
526 for (auto& v : outputs) {
527 graphData.graph->dataTensor.emplace_back(std::move(v));
528 }
529 return StatusCode::SUCCESS;
530}
Gaudi::Property< bool > m_sanitizeNonFinitePredictions
row
Appending html table to final .html summary file.

◆ setupModel()

StatusCode BucketInferenceToolBase::setupModel ( )
protectedinherited

Definition at line 69 of file BucketInferenceToolBase.cxx.

69 {
70 ATH_CHECK(m_onnxSessionTool.retrieve());
71 ATH_CHECK(m_readKey.initialize());
72 ATH_CHECK(m_geoCtxKey.initialize());
73
74 const InferenceUtils::SessionBackend backend = InferenceUtils::sessionBackend(m_onnxSessionTool);
75 m_isCuda = backend.isCuda;
76 m_cudaDeviceId = backend.cudaDeviceId;
77 if (m_isCuda) {
78 ATH_MSG_INFO("ONNX session is running on CUDA device " << m_cudaDeviceId
79 << ". I/O binding will be used.");
80 } else {
81 ATH_MSG_INFO("ONNX session is running on CPU.");
82 }
83
84 return StatusCode::SUCCESS;
85}
SessionBackend sessionBackend(const SessionToolHandle &sessionTool)

◆ trimFeatureToken()

std::string BucketInferenceToolBase::trimFeatureToken ( std::string s)
staticprotectedinherited

Definition at line 25 of file BucketInferenceToolBase.cxx.

25 {
26 auto notSpace = [](unsigned char c) { return !std::isspace(c); };
27 s.erase(s.begin(), std::find_if(s.begin(), s.end(), notSpace));
28 s.erase(std::find_if(s.rbegin(), s.rend(), notSpace).base(), s.end());
29 return s;
30}

Member Data Documentation

◆ kBucketFeatureCount

std::size_t MuonML::BucketInferenceToolBase::kBucketFeatureCount = 6
staticconstexprprotectedinherited

Definition at line 53 of file BucketInferenceToolBase.h.

◆ kDefaultNodeFeatureNames

std::array<std::string_view, kNodeFeatureCount> MuonML::BucketInferenceToolBase::kDefaultNodeFeatureNames
staticconstexprprotectedinherited
Initial value:
= {
"segmentPositionX_m", "segmentPositionY_m", "segmentPositionZ_m",
"segmentDirectionX", "segmentDirectionY", "segmentDirectionZ",
"bucket_chamberIndex", "bucket_layers", "bucket_sector", "bucket_segments"}

Definition at line 56 of file BucketInferenceToolBase.h.

56 {
57 "segmentPositionX_m", "segmentPositionY_m", "segmentPositionZ_m",
58 "segmentDirectionX", "segmentDirectionY", "segmentDirectionZ",
59 "bucket_chamberIndex", "bucket_layers", "bucket_sector", "bucket_segments"};

◆ kEdgeFeatureCount

std::size_t MuonML::BucketInferenceToolBase::kEdgeFeatureCount = 7
staticconstexprprotectedinherited

Definition at line 55 of file BucketInferenceToolBase.h.

◆ kNodeFeatureCount

std::size_t MuonML::BucketInferenceToolBase::kNodeFeatureCount = 10
staticconstexprprotectedinherited

Definition at line 54 of file BucketInferenceToolBase.h.

◆ m_cosMin

float MuonML::SegmentEdgeClassifierTool::m_cosMin {0.f}
private

Definition at line 100 of file SegmentEdgeClassifierTool.h.

100{0.f};

◆ m_cudaDeviceId

int MuonML::BucketInferenceToolBase::m_cudaDeviceId {0}
protectedinherited

Definition at line 102 of file BucketInferenceToolBase.h.

102{0};

◆ m_debugDumpEvents

std::atomic<unsigned int> MuonML::SegmentEdgeClassifierTool::m_debugDumpEvents {0}
mutableprivate

Definition at line 107 of file SegmentEdgeClassifierTool.h.

107{0};

◆ m_debugDumpFile

Gaudi::Property<std::string> MuonML::SegmentEdgeClassifierTool::m_debugDumpFile {this, "DebugDumpFile", ""}
private

Definition at line 98 of file SegmentEdgeClassifierTool.h.

98{this, "DebugDumpFile", ""};

◆ m_debugDumpFirstNEdges

Gaudi::Property<unsigned int> MuonML::BucketInferenceToolBase::m_debugDumpFirstNEdges {this, "DebugDumpFirstNEdges", 12}
protectedinherited

Definition at line 94 of file BucketInferenceToolBase.h.

94{this, "DebugDumpFirstNEdges", 12};

◆ m_debugDumpFirstNNodes

Gaudi::Property<unsigned int> MuonML::BucketInferenceToolBase::m_debugDumpFirstNNodes {this, "DebugDumpFirstNNodes", 5}
protectedinherited

Definition at line 93 of file BucketInferenceToolBase.h.

93{this, "DebugDumpFirstNNodes", 5};

◆ m_debugDumpMaxEvents

Gaudi::Property<unsigned int> MuonML::SegmentEdgeClassifierTool::m_debugDumpMaxEvents {this, "DebugDumpMaxEvents", 0}
private

Definition at line 99 of file SegmentEdgeClassifierTool.h.

99{this, "DebugDumpMaxEvents", 0};

◆ m_debugDumpMutex

std::mutex MuonML::SegmentEdgeClassifierTool::m_debugDumpMutex
mutableprivate

Definition at line 106 of file SegmentEdgeClassifierTool.h.

◆ m_geoCtxKey

ActsTrk::GeoContextReadKey_t MuonML::BucketInferenceToolBase::m_geoCtxKey {this, "AlignmentKey", "ActsAlignment", "cond handle key"}
protectedinherited

Definition at line 80 of file BucketInferenceToolBase.h.

80{this, "AlignmentKey", "ActsAlignment", "cond handle key"};

◆ m_inputEdgeAttrName

Gaudi::Property<std::string> MuonML::SegmentEdgeClassifierTool::m_inputEdgeAttrName {this, "InputEdgeAttrName", "edge_attr"}
private

Definition at line 96 of file SegmentEdgeClassifierTool.h.

96{this, "InputEdgeAttrName", "edge_attr"};

◆ m_inputEdgeIndexName

Gaudi::Property<std::string> MuonML::SegmentEdgeClassifierTool::m_inputEdgeIndexName {this, "InputEdgeIndexName", "edge_index"}
private

Definition at line 95 of file SegmentEdgeClassifierTool.h.

95{this, "InputEdgeIndexName", "edge_index"};

◆ m_inputNodeName

Gaudi::Property<std::string> MuonML::SegmentEdgeClassifierTool::m_inputNodeName {this, "InputNodeName", "x"}
private

Definition at line 94 of file SegmentEdgeClassifierTool.h.

94{this, "InputNodeName", "x"};

◆ m_isCuda

bool MuonML::BucketInferenceToolBase::m_isCuda {false}
protectedinherited

Definition at line 101 of file BucketInferenceToolBase.h.

101{false};

◆ m_maxAbsDz

Gaudi::Property<double> MuonML::BucketInferenceToolBase::m_maxAbsDz {this, "MaxAbsDz", 15000.0}
protectedinherited

Definition at line 90 of file BucketInferenceToolBase.h.

90{this, "MaxAbsDz", 15000.0};

◆ m_maxChamberDelta

Gaudi::Property<int> MuonML::BucketInferenceToolBase::m_maxChamberDelta {this, "MaxChamberDelta", 13}
protectedinherited

Definition at line 87 of file BucketInferenceToolBase.h.

87{this, "MaxChamberDelta", 13};

◆ m_maxDeltaSector

Gaudi::Property<int> MuonML::SegmentEdgeClassifierTool::m_maxDeltaSector {this, "MaxDeltaSector", 1}
private

Definition at line 92 of file SegmentEdgeClassifierTool.h.

92{this, "MaxDeltaSector", 1};

◆ m_maxDeltaThetaDeg

Gaudi::Property<float> MuonML::SegmentEdgeClassifierTool::m_maxDeltaThetaDeg {this, "MaxDeltaThetaDeg", 35.f}
private

Definition at line 91 of file SegmentEdgeClassifierTool.h.

91{this, "MaxDeltaThetaDeg", 35.f};

◆ m_maxDistXY

Gaudi::Property<double> MuonML::BucketInferenceToolBase::m_maxDistXY {this, "MaxDistXY", 6800.0}
protectedinherited

Definition at line 89 of file BucketInferenceToolBase.h.

89{this, "MaxDistXY", 6800.0};

◆ m_maxSectorDelta

Gaudi::Property<int> MuonML::BucketInferenceToolBase::m_maxSectorDelta {this, "MaxSectorDelta", 1}
protectedinherited

Definition at line 88 of file BucketInferenceToolBase.h.

88{this, "MaxSectorDelta", 1};

◆ m_minLayers

Gaudi::Property<int> MuonML::BucketInferenceToolBase::m_minLayers {this, "MinLayersValid", 3}
protectedinherited

Definition at line 86 of file BucketInferenceToolBase.h.

86{this, "MinLayersValid", 3};

◆ m_nodeFeatureIds

std::vector<SegmentNodeFeatureId> MuonML::SegmentEdgeClassifierTool::m_nodeFeatureIds {}
private

Definition at line 104 of file SegmentEdgeClassifierTool.h.

104{};

◆ m_nodeFeatureNames

std::vector<std::string> MuonML::SegmentEdgeClassifierTool::m_nodeFeatureNames {}
private

Node feature order expected by the model metadata (resolved at initialize).

Definition at line 103 of file SegmentEdgeClassifierTool.h.

103{};

◆ m_onnxSessionTool

ToolHandle<AthOnnx::IOnnxRuntimeSessionTool> MuonML::BucketInferenceToolBase::m_onnxSessionTool
privateinherited
Initial value:
{
this, "ModelSession", ""}

Definition at line 105 of file BucketInferenceToolBase.h.

105 {
106 this, "ModelSession", ""};

◆ m_outputName

Gaudi::Property<std::string> MuonML::SegmentEdgeClassifierTool::m_outputName {this, "OutputName", "logits"}
private

Definition at line 97 of file SegmentEdgeClassifierTool.h.

97{this, "OutputName", "logits"};

◆ m_readKey

SG::ReadHandleKey<MuonR4::SpacePointContainer> MuonML::BucketInferenceToolBase::m_readKey {this, "ReadSpacePoints", "MuonSpacePoints"}
protectedinherited

Definition at line 79 of file BucketInferenceToolBase.h.

79{this, "ReadSpacePoints", "MuonSpacePoints"};

◆ m_sanitizeNonFinitePredictions

Gaudi::Property<bool> MuonML::BucketInferenceToolBase::m_sanitizeNonFinitePredictions
protectedinherited
Initial value:
{
this, "SanitizeNonFinitePredictions", false,
"When true, replace non-finite ONNX outputs with -100 and log a warning."}

Definition at line 96 of file BucketInferenceToolBase.h.

96 {
97 this, "SanitizeNonFinitePredictions", false,
98 "When true, replace non-finite ONNX outputs with -100 and log a warning."};

◆ m_sectorModulo

Gaudi::Property<int> MuonML::SegmentEdgeClassifierTool::m_sectorModulo {this, "SectorModulo", 16}
private

Definition at line 93 of file SegmentEdgeClassifierTool.h.

93{this, "SectorModulo", 16};

◆ m_validateEdges

Gaudi::Property<bool> MuonML::BucketInferenceToolBase::m_validateEdges {this, "ValidateEdges", true}
protectedinherited

Definition at line 95 of file BucketInferenceToolBase.h.

95{this, "ValidateEdges", true};

The documentation for this class was generated from the following files: