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
Gaudi::Property< unsigned int > m_maxSegmentsPerBucket
Gaudi::Property< unsigned int > m_maxEdgesPerNodeBeforeInference
Gaudi::Property< unsigned int > m_maxEdgesPerTargetChamberBeforeInference
Gaudi::Property< bool > m_dropSameChamberEdgesBeforeInference
Gaudi::Property< bool > m_dropIsolatedNodesBeforeInference
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_DEBUG(x,...)
#define ATH_MSG_ERROR(x,...)
#define ATH_MSG_WARNING(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 231 of file SegmentEdgeClassifierTool.cxx.

233 {
234 graph = SegmentEdgeGraph{};
235 graph.segments.reserve(segments.size());
236 graph.nodeFeatures.reserve(segments.size() * kNodeFeatureCount);
237
238 /*
239 * Keep the original bucket multiplicity in the node feature even when the
240 * speed configuration retains only the best representatives of a bucket.
241 * This preserves the model's occupancy input while removing duplicate node
242 * and edge work before tensor construction.
243 */
244 std::map<SegmentBucketKey, std::vector<const xAOD::MuonSegment*>>
245 segmentsByBucket;
246 for (const xAOD::MuonSegment* segment : segments) {
247 segmentsByBucket[segmentBucketKey(*segment)].push_back(segment);
248 }
249
250 const InferenceUtils::SegmentQualityOrder betterSegment{};
251
252 std::unordered_set<const xAOD::MuonSegment*> retainedSegments;
253 retainedSegments.reserve(segments.size());
254 for (auto& [_, bucketSegments] : segmentsByBucket) {
255 std::ranges::sort(bucketSegments, betterSegment);
256 const std::size_t nKeep = m_maxSegmentsPerBucket.value() == 0
257 ? bucketSegments.size()
258 : std::min<std::size_t>(
259 bucketSegments.size(),
260 m_maxSegmentsPerBucket.value());
261 retainedSegments.insert(bucketSegments.begin(),
262 bucketSegments.begin() + nKeep);
263 }
264
265 std::vector<Amg::Vector3D> pos;
266 std::vector<Amg::Vector3D> dir;
267 std::vector<BucketSegmentFeatures> bucket;
268 pos.reserve(retainedSegments.size());
269 dir.reserve(retainedSegments.size());
270 bucket.reserve(retainedSegments.size());
271
272 for (const xAOD::MuonSegment* segment : segments) {
273 if (!retainedSegments.contains(segment)) continue;
274
275 const Amg::Vector3D position = segment->position();
276 const Amg::Vector3D direction = segment->direction();
277 const SegmentBucketKey key = segmentBucketKey(*segment);
278 const auto bucketIt = segmentsByBucket.find(key);
279 const int multiplicity =
280 bucketIt == segmentsByBucket.end()
281 ? 1
282 : static_cast<int>(bucketIt->second.size());
283
284 const int chamberIndex = static_cast<int>(segment->chamberIndex());
285 const int layers = layersInBucket(*MuonR4::detailedSegment(*segment)->parent()->parentBucket());
286 const int sector = segment->sector();
287
288 graph.segments.push_back(segment);
289 pos.emplace_back(position / Gaudi::Units::m);
290 dir.emplace_back(direction);
291 bucket.emplace_back(BucketSegmentFeatures{
292 chamberIndex, layers, sector, multiplicity});
293 for (const SegmentNodeFeatureId featureId : m_nodeFeatureIds) {
294 graph.nodeFeatures.push_back(
295 nodeFeatureValue(featureId, pos.back(), dir.back(), bucket.back()));
296 }
297 }
298 graph.nNodes = graph.segments.size();
299
300 if (pos.size() != graph.nNodes || dir.size() != graph.nNodes ||
301 bucket.size() != graph.nNodes) {
302 ATH_MSG_ERROR("Inconsistent vector sizes during graph building: nodes="
303 << graph.nNodes << ", pos=" << pos.size()
304 << ", dir=" << dir.size() << ", bucket=" << bucket.size());
305 return StatusCode::FAILURE;
306 }
307
308 if (graph.nNodes < 2) {
309 graph.nEdges = 0;
310 return StatusCode::SUCCESS;
311 }
312
313 const auto wrapRegularSector = [&](int sector) {
314 // MuonSegment::sector() is a regular sector number, not an ExpandedSector
315 // coordinate. Wrap it to [0, modulo); <= 0 disables wrapping.
316 if (m_sectorModulo.value() > 0) {
317 sector %= m_sectorModulo.value();
318 if (sector < 0) sector += m_sectorModulo.value();
319 }
320 return sector;
321 };
322
323 // The lookup key must use the same wrapping as the target sectors below:
324 // ATLAS sectors are 1-based (1..16), so a raw key of 16 can never match a
325 // wrapped target of 0, which silently dropped every edge into sector 16.
326 // The per-pair sectorDistance check below enforces the true circular
327 // distance on the raw sector numbers.
328 std::unordered_map<int, std::vector<std::size_t>> nodesBySector;
329 nodesBySector.reserve(graph.nNodes);
330 for (std::size_t node = 0; node < graph.nNodes; ++node) {
331 nodesBySector[wrapRegularSector(bucket[node].sector)].push_back(node);
332 }
333
334 std::unordered_map<int, std::vector<int>> targetSectorsBySourceSector;
335 targetSectorsBySourceSector.reserve(nodesBySector.size());
336 std::size_t sectorLocalEdgeUpperBound = 0;
337 for (const auto& [sourceSector, sourceNodes] : nodesBySector) {
338 std::vector<int> targetSectors;
339 targetSectors.reserve(2 * m_maxDeltaSector.value() + 1);
340 for (int delta = -m_maxDeltaSector.value();
341 delta <= m_maxDeltaSector.value(); ++delta) {
342 targetSectors.push_back(wrapRegularSector(sourceSector + delta));
343 }
344 for (const int targetSector : targetSectors) {
345 const auto found = nodesBySector.find(targetSector);
346 if (found == nodesBySector.end()) continue;
347 sectorLocalEdgeUpperBound += sourceNodes.size() * found->second.size();
348 if (targetSector == sourceSector) {
349 sectorLocalEdgeUpperBound -= sourceNodes.size();
350 }
351 }
352 targetSectorsBySourceSector.emplace(sourceSector,
353 std::move(targetSectors));
354 }
355
356 /*
357 * The model receives a directed graph, but the geometric candidate relation
358 * is undirected. Build each pair once, then emit both directions. With a
359 * non-zero input cap, each endpoint nominates its best candidates and the
360 * union is made bidirectional before inference; this preserves the message
361 * passing symmetry expected by the GNN.
362 */
363 struct UndirectedEdge {
364 std::size_t first{0};
365 std::size_t second{0};
366 float dx{0.f};
367 float dy{0.f};
368 float dz{0.f};
369 float distance{0.f};
370 float cosAngle{0.f};
371 };
372 const auto betterEdge = [](const UndirectedEdge& first,
373 const UndirectedEdge& second) {
374 const int cosOrder = InferenceUtils::compareFloatDescending(
375 first.cosAngle, second.cosAngle);
376 if (cosOrder != 0) {
377 return cosOrder < 0;
378 }
379
380 const int distanceOrder =
381 InferenceUtils::compareFloat(first.distance, second.distance);
382 if (distanceOrder != 0) {
383 return distanceOrder < 0;
384 }
385 if (first.first != second.first) return first.first < second.first;
386 return first.second < second.second;
387 };
388 const auto edgeKey = [](const UndirectedEdge& edge) {
389 return (static_cast<std::uint64_t>(edge.first) << 32) |
390 static_cast<std::uint64_t>(edge.second);
391 };
392
393 const unsigned int maxEdgesPerNode =
395 const unsigned int maxEdgesPerTargetChamber =
397 const bool usePreInferenceSelection =
398 maxEdgesPerNode != 0 || maxEdgesPerTargetChamber != 0;
399 std::vector<std::vector<UndirectedEdge>> bestEdgesByNode;
400 if (usePreInferenceSelection) {
401 bestEdgesByNode.resize(graph.nNodes);
402 const unsigned int reservePerNode =
403 maxEdgesPerNode != 0 ? maxEdgesPerNode : maxEdgesPerTargetChamber;
404 for (std::vector<UndirectedEdge>& edges : bestEdgesByNode) {
405 edges.reserve(reservePerNode);
406 }
407 } else {
408 graph.edgeIndex.reserve(2 * sectorLocalEdgeUpperBound);
409 graph.edgeFeatures.reserve(kEdgeFeatureCount * sectorLocalEdgeUpperBound);
410 }
411 const auto appendDirectedPair = [&](const UndirectedEdge& edge) {
412 graph.edgeIndex.push_back(static_cast<int64_t>(edge.first));
413 graph.edgeIndex.push_back(static_cast<int64_t>(edge.second));
414 graph.edgeFeatures.insert(
415 graph.edgeFeatures.end(),
416 {edge.dx, edge.dy, edge.dz, edge.distance, edge.cosAngle,
417 float(bucket[edge.first].chamberIndex ==
418 bucket[edge.second].chamberIndex),
419 float(bucket[edge.first].sector == bucket[edge.second].sector)});
420
421 graph.edgeIndex.push_back(static_cast<int64_t>(edge.second));
422 graph.edgeIndex.push_back(static_cast<int64_t>(edge.first));
423 graph.edgeFeatures.insert(
424 graph.edgeFeatures.end(),
425 {-edge.dx, -edge.dy, -edge.dz, edge.distance, edge.cosAngle,
426 float(bucket[edge.first].chamberIndex ==
427 bucket[edge.second].chamberIndex),
428 float(bucket[edge.first].sector == bucket[edge.second].sector)});
429 };
430
431 const auto retainForNode = [&](std::size_t node,
432 const UndirectedEdge& candidate) {
433 std::vector<UndirectedEdge>& retained = bestEdgesByNode[node];
434 const std::size_t other = candidate.first == node ? candidate.second
435 : candidate.first;
436 const int targetChamber = bucket[other].chamberIndex;
437
438 if (maxEdgesPerTargetChamber != 0) {
439 unsigned int sameChamberCount = 0;
440 auto worstSameChamber = retained.end();
441 for (auto it = retained.begin(); it != retained.end(); ++it) {
442 const std::size_t retainedOther =
443 it->first == node ? it->second : it->first;
444 if (bucket[retainedOther].chamberIndex != targetChamber) continue;
445 ++sameChamberCount;
446 if (worstSameChamber == retained.end() ||
447 betterEdge(*worstSameChamber, *it)) {
448 worstSameChamber = it;
449 }
450 }
451 if (sameChamberCount >= maxEdgesPerTargetChamber) {
452 if (!betterEdge(candidate, *worstSameChamber)) return;
453 *worstSameChamber = candidate;
454 } else {
455 retained.push_back(candidate);
456 }
457 } else {
458 retained.push_back(candidate);
459 }
460
461 if (maxEdgesPerNode != 0 && retained.size() > maxEdgesPerNode) {
462 auto worst = retained.begin();
463 for (auto it = std::next(retained.begin()); it != retained.end(); ++it) {
464 if (betterEdge(*worst, *it)) worst = it;
465 }
466 retained.erase(worst);
467 }
468 };
469
470 std::size_t candidatePairs = 0;
471 for (std::size_t first = 0; first < graph.nNodes; ++first) {
472 const auto sectorsIt =
473 targetSectorsBySourceSector.find(wrapRegularSector(bucket[first].sector));
474 if (sectorsIt == targetSectorsBySourceSector.end()) continue;
475 for (const int sector : sectorsIt->second) {
476 const auto targetIt = nodesBySector.find(sector);
477 if (targetIt == nodesBySector.end()) continue;
478
479 for (const std::size_t second : targetIt->second) {
480 // Every valid pair will be visited from the lower-index endpoint.
481 if (second <= first) continue;
482 if (sectorDistance(bucket[first].sector, bucket[second].sector,
483 m_sectorModulo.value()) >
484 m_maxDeltaSector.value()) {
485 continue;
486 }
488 bucket[first].chamberIndex == bucket[second].chamberIndex) {
489 continue;
490 }
491 const float cosAngle = static_cast<float>(dir[first].dot(dir[second]));
492 if (cosAngle < m_cosMin) continue;
493
494 const Amg::Vector3D delta = pos[second] - pos[first];
495 const UndirectedEdge candidate{
496 first,
497 second,
498 static_cast<float>(delta.x()),
499 static_cast<float>(delta.y()),
500 static_cast<float>(delta.z()),
501 static_cast<float>(delta.mag()),
502 cosAngle};
503 ++candidatePairs;
504
505 if (!usePreInferenceSelection) {
506 appendDirectedPair(candidate);
507 } else {
508 retainForNode(first, candidate);
509 retainForNode(second, candidate);
510 }
511 }
512 }
513 }
514 std::size_t retainedPairs = candidatePairs;
515 if (usePreInferenceSelection) {
516 const unsigned int selectedReservePerNode =
517 maxEdgesPerNode != 0 ? maxEdgesPerNode : maxEdgesPerTargetChamber;
518 std::unordered_set<std::uint64_t> selectedKeys;
519 selectedKeys.reserve(graph.nNodes * selectedReservePerNode);
520 std::vector<UndirectedEdge> selectedEdges;
521 selectedEdges.reserve(graph.nNodes * selectedReservePerNode);
522
523 for (const std::vector<UndirectedEdge>& nodeEdges : bestEdgesByNode) {
524 for (const UndirectedEdge& edge : nodeEdges) {
525 if (selectedKeys.insert(edgeKey(edge)).second) {
526 selectedEdges.push_back(edge);
527 }
528 }
529 }
530 std::sort(selectedEdges.begin(), selectedEdges.end(),
531 [](const UndirectedEdge& first,
532 const UndirectedEdge& second) {
533 if (first.first != second.first) {
534 return first.first < second.first;
535 }
536 return first.second < second.second;
537 });
538
539 retainedPairs = selectedEdges.size();
540 graph.edgeIndex.reserve(4 * retainedPairs);
541 graph.edgeFeatures.reserve(2 * kEdgeFeatureCount * retainedPairs);
542 for (const UndirectedEdge& edge : selectedEdges) {
543 appendDirectedPair(edge);
544 }
545 }
546 graph.nEdges = graph.edgeIndex.size() / 2;
547 const std::size_t nodesBeforeIsolatedNodeDrop = graph.nNodes;
548 if (m_dropIsolatedNodesBeforeInference.value() && graph.nEdges != 0) {
549 std::vector<unsigned char> active(graph.nNodes, 0);
550 for (const int64_t index : graph.edgeIndex) {
551 active[static_cast<std::size_t>(index)] = 1;
552 }
553 const std::size_t activeNodes =
554 std::count(active.begin(), active.end(), static_cast<unsigned char>(1));
555 if (activeNodes != graph.nNodes) {
556 std::vector<std::size_t> oldToNew(graph.nNodes, graph.nNodes);
557 std::vector<const xAOD::MuonSegment*> compactedSegments;
558 std::vector<float> compactedNodeFeatures;
559 compactedSegments.reserve(activeNodes);
560 compactedNodeFeatures.reserve(activeNodes * kNodeFeatureCount);
561 for (std::size_t oldNode = 0; oldNode < graph.nNodes; ++oldNode) {
562 if (!active[oldNode]) continue;
563 oldToNew[oldNode] = compactedSegments.size();
564 compactedSegments.push_back(graph.segments[oldNode]);
565 const auto featureBegin = graph.nodeFeatures.begin() +
566 oldNode * kNodeFeatureCount;
567 compactedNodeFeatures.insert(compactedNodeFeatures.end(),
568 featureBegin,
569 featureBegin + kNodeFeatureCount);
570 }
571 for (int64_t& index : graph.edgeIndex) {
572 index = static_cast<int64_t>(oldToNew[static_cast<std::size_t>(index)]);
573 }
574 graph.segments = std::move(compactedSegments);
575 graph.nodeFeatures = std::move(compactedNodeFeatures);
576 graph.nNodes = activeNodes;
577 }
578 }
579 ATH_MSG_DEBUG("buildGraph: input segments=" << segments.size()
580 << ", kept nodes=" << graph.nNodes
581 << ", nodes before isolated-node drop=" << nodesBeforeIsolatedNodeDrop
582 << ", bucket cap=" << m_maxSegmentsPerBucket.value()
583 << ", candidate pairs=" << candidatePairs
584 << ", retained pairs=" << retainedPairs
585 << ", built directed edges=" << graph.nEdges
586 << ", pre-inference node cap=" << m_maxEdgesPerNodeBeforeInference.value()
587 << ", per-target-chamber cap=" << maxEdgesPerTargetChamber
588 << ", drop same chamber=" << m_dropSameChamberEdgesBeforeInference.value()
589 << ", drop isolated nodes=" << m_dropIsolatedNodesBeforeInference.value()
590 << ", sector-local reserve=" << sectorLocalEdgeUpperBound);
591 return StatusCode::SUCCESS;
592}
static constexpr std::size_t kEdgeFeatureCount
static constexpr std::size_t kNodeFeatureCount
Gaudi::Property< unsigned int > m_maxEdgesPerNodeBeforeInference
Gaudi::Property< unsigned int > m_maxEdgesPerTargetChamberBeforeInference
Gaudi::Property< unsigned int > m_maxSegmentsPerBucket
Gaudi::Property< bool > m_dropSameChamberEdgesBeforeInference
std::vector< SegmentNodeFeatureId > m_nodeFeatureIds
Gaudi::Property< bool > m_dropIsolatedNodesBeforeInference
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.
float distance(const Amg::Vector3D &p1, const Amg::Vector3D &p2)
calculates the distance between two point in 3D space
Eigen::Matrix< double, 3, 1 > Vector3D
bool first
Definition DeMoScan.py:534
str index
Definition DeMoScan.py:362
int compareFloat(float first, float second)
Three-way float comparison which orders NaN after all numeric values.
int compareFloatDescending(float first, float second)
Three-way descending comparison which also orders NaN last.
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.
const Amg::Vector3D & direction() const
Method to retrieve the direction at the Intersection.
const Amg::Vector3D & position() const
Method to retrieve the position of the Intersection.
@ active
Definition Layer.h:47
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.
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 594 of file SegmentEdgeClassifierTool.cxx.

596 {
597 scores.clear();
598 if (!graph.nNodes) return StatusCode::SUCCESS;
599 if (!graph.nEdges) {
600 ATH_CHECK(dumpDebugEvent(ctx, graph, scores));
601 return StatusCode::SUCCESS;
602 }
603
604 if (graph.nodeFeatures.size() != graph.nNodes * kNodeFeatureCount) {
605 ATH_MSG_ERROR("Unexpected node feature size " << graph.nodeFeatures.size()
606 << "; expected " << (graph.nNodes * kNodeFeatureCount));
607 return StatusCode::FAILURE;
608 }
609 if (graph.edgeIndex.size() != 2 * graph.nEdges) {
610 ATH_MSG_ERROR("Unexpected edge index size " << graph.edgeIndex.size()
611 << "; expected " << (2 * graph.nEdges));
612 return StatusCode::FAILURE;
613 }
614 if (graph.edgeFeatures.size() != graph.nEdges * kEdgeFeatureCount) {
615 ATH_MSG_ERROR("Unexpected edge feature size " << graph.edgeFeatures.size()
616 << "; expected " << (graph.nEdges * kEdgeFeatureCount));
617 return StatusCode::FAILURE;
618 }
619
620 GraphRawData raw{};
621 raw.graph = std::make_unique<InferenceGraph>();
622 raw.edgeIndexPacked.resize(2 * graph.nEdges);
623 for (std::size_t e = 0; e < graph.nEdges; ++e) {
624 raw.edgeIndexPacked[e] = graph.edgeIndex[2 * e];
625 raw.edgeIndexPacked[graph.nEdges + e] = graph.edgeIndex[2 * e + 1];
626 }
627
628 Ort::MemoryInfo memInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
629
630 const std::vector<int64_t> nodeShape{static_cast<int64_t>(graph.nNodes), static_cast<int64_t>(kNodeFeatureCount)};
631 // The graph outlives the synchronous ONNX call below. Use its node
632 // buffer directly instead of allocating and copying featureLeaves per event.
633 ATLAS_THREAD_SAFE float* nodeFeaturesData =
634 const_cast<float*>(graph.nodeFeatures.data());
635 raw.graph->dataTensor.emplace_back(
636 Ort::Value::CreateTensor<float>(memInfo,
637 nodeFeaturesData,
638 graph.nodeFeatures.size(),
639 nodeShape.data(),
640 nodeShape.size()));
641
642 const std::vector<int64_t> edgeIndexShape{2, static_cast<int64_t>(graph.nEdges)};
643 raw.graph->dataTensor.emplace_back(
644 Ort::Value::CreateTensor<int64_t>(memInfo,
645 raw.edgeIndexPacked.data(),
646 raw.edgeIndexPacked.size(),
647 edgeIndexShape.data(),
648 edgeIndexShape.size()));
649
650 // ONNX Runtime's CreateTensor API takes a non-const pointer, but it does not
651 // mutate input buffers during inference. Avoid copying edge_attr every event.
652 ATLAS_THREAD_SAFE float* edgeFeaturesData = const_cast<float*>(graph.edgeFeatures.data());
653 const std::vector<int64_t> edgeAttrShape{static_cast<int64_t>(graph.nEdges), static_cast<int64_t>(kEdgeFeatureCount)};
654 raw.graph->dataTensor.emplace_back(
655 Ort::Value::CreateTensor<float>(memInfo,
656 edgeFeaturesData,
657 graph.edgeFeatures.size(),
658 edgeAttrShape.data(),
659 edgeAttrShape.size()));
660
661 const std::vector<const char*> inputNames{
662 m_inputNodeName.value().c_str(),
663 m_inputEdgeIndexName.value().c_str(),
664 m_inputEdgeAttrName.value().c_str()};
665 const std::vector<const char*> outputNames{m_outputName.value().c_str()};
666 ATH_MSG_DEBUG("classifyEdges: ONNX inputs shapes x=[" << nodeShape[0] << "," << nodeShape[1]
667 << "], edge_index=[" << edgeIndexShape[0] << "," << edgeIndexShape[1]
668 << "], edge_attr=[" << edgeAttrShape[0] << "," << edgeAttrShape[1] << "]");
669 ATH_CHECK(runNamedInference(raw, inputNames, outputNames));
670
671 if (raw.graph->dataTensor.size() <= inputNames.size()) {
672 ATH_MSG_ERROR("Missing ONNX output tensor for segment edge inference");
673 return StatusCode::FAILURE;
674 }
675
676 const Ort::Value& outTensor = raw.graph->dataTensor[inputNames.size()];
677 const auto outInfo = outTensor.GetTensorTypeAndShapeInfo();
678 const std::vector<int64_t> outShape = outInfo.GetShape();
679 const size_t outSize = outInfo.GetElementCount();
680 if (!outShape.empty()) {
681 ATH_MSG_DEBUG("classifyEdges: ONNX output rank=" << outShape.size()
682 << ", first dim=" << outShape.front()
683 << ", elements=" << outSize);
684 } else {
685 ATH_MSG_DEBUG("classifyEdges: ONNX scalar output, elements=" << outSize);
686 }
687 if (outSize < graph.nEdges) {
688 ATH_MSG_ERROR("ONNX logits tensor has " << outSize << " entries for " << graph.nEdges << " edges");
689 return StatusCode::FAILURE;
690 }
691
692 const float* logits = outTensor.GetTensorData<float>();
693 scores.reserve(graph.nEdges);
694 for (std::size_t e=0; e<graph.nEdges; ++e) {
695 const float l = logits[e];
696 scores.push_back({std::size_t(graph.edgeIndex[2 * e]),
697 std::size_t(graph.edgeIndex[2 * e + 1]),
698 l,
700 }
701
702 ATH_CHECK(dumpDebugEvent(ctx, graph, scores));
703 return StatusCode::SUCCESS;
704}
#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 706 of file SegmentEdgeClassifierTool.cxx.

709 {
710 if (m_debugDumpFile.value().empty()) return StatusCode::SUCCESS;
711
712 std::lock_guard<std::mutex> lock{m_debugDumpMutex};
713 if (m_debugDumpMaxEvents.value() != 0 &&
714 m_debugDumpEvents.load(std::memory_order_relaxed) >=
715 m_debugDumpMaxEvents.value()) {
716 return StatusCode::SUCCESS;
717 }
718
719 if (graph.nodeFeatures.size() != graph.nNodes * kNodeFeatureCount ||
720 graph.edgeIndex.size() != graph.nEdges * 2 ||
721 graph.edgeFeatures.size() != graph.nEdges * kEdgeFeatureCount ||
722 scores.size() != graph.nEdges) {
723 ATH_MSG_ERROR("Cannot write segment-edge debug dump: inconsistent graph/output sizes"
724 << " nodes=" << graph.nNodes
725 << " nodeFeatures=" << graph.nodeFeatures.size()
726 << " edges=" << graph.nEdges
727 << " edgeIndex=" << graph.edgeIndex.size()
728 << " edgeFeatures=" << graph.edgeFeatures.size()
729 << " scores=" << scores.size());
730 return StatusCode::FAILURE;
731 }
732
733 nlohmann::json x = nlohmann::json::array();
734 x.get_ref<nlohmann::json::array_t&>().reserve(graph.nodeFeatures.size());
735 for (const float value : graph.nodeFeatures) {
736 x.push_back(std::isfinite(value) ? nlohmann::json(value)
737 : nlohmann::json(nullptr));
738 }
739
740 nlohmann::json edgeIndex = nlohmann::json::array();
741 edgeIndex.get_ref<nlohmann::json::array_t&>().reserve(graph.nEdges * 2);
742 // This is the actual ONNX [2,E] row-major buffer: all sources then all destinations.
743 for (std::size_t edge = 0; edge < graph.nEdges; ++edge) {
744 edgeIndex.push_back(graph.edgeIndex[2 * edge]);
745 }
746 for (std::size_t edge = 0; edge < graph.nEdges; ++edge) {
747 edgeIndex.push_back(graph.edgeIndex[2 * edge + 1]);
748 }
749
750 nlohmann::json edgeAttr = nlohmann::json::array();
751 edgeAttr.get_ref<nlohmann::json::array_t&>().reserve(graph.edgeFeatures.size());
752 for (const float value : graph.edgeFeatures) {
753 edgeAttr.push_back(std::isfinite(value) ? nlohmann::json(value)
754 : nlohmann::json(nullptr));
755 }
756
757 nlohmann::json logits = nlohmann::json::array();
758 nlohmann::json probabilities = nlohmann::json::array();
759 nlohmann::json edgeSrc = nlohmann::json::array();
760 nlohmann::json edgeDst = nlohmann::json::array();
761 logits.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
762 probabilities.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
763 edgeSrc.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
764 edgeDst.get_ref<nlohmann::json::array_t&>().reserve(scores.size());
765 for (const SegmentEdgeScore& score : scores) {
766 edgeSrc.push_back(score.src);
767 edgeDst.push_back(score.dst);
768 logits.push_back(std::isfinite(score.logit) ? nlohmann::json(score.logit)
769 : nlohmann::json(nullptr));
770 probabilities.push_back(std::isfinite(score.probability)
771 ? nlohmann::json(score.probability)
772 : nlohmann::json(nullptr));
773 }
774
775 std::ofstream out{m_debugDumpFile.value(), std::ios::out | std::ios::app};
776 if (!out) {
777 ATH_MSG_ERROR("Could not append to segment-edge debug dump file: "
778 << m_debugDumpFile.value());
779 return StatusCode::FAILURE;
780 }
781
782 const unsigned int dumpIndex =
783 m_debugDumpEvents.fetch_add(1, std::memory_order_relaxed);
784 nlohmann::ordered_json event;
785 event["record_type"] = "event";
786 event["format_version"] = 1;
787 event["dump_index"] = dumpIndex;
788 event["run_number"] = ctx.eventID().run_number();
789 event["lumi_block"] = ctx.eventID().lumi_block();
790 event["event_number"] = ctx.eventID().event_number();
791 event["slot"] = ctx.slot();
792 event["n_nodes"] = graph.nNodes;
793 event["n_edges"] = graph.nEdges;
794 event["x_shape"] = {graph.nNodes, kNodeFeatureCount};
795 event["edge_index_shape"] = {2, graph.nEdges};
796 event["edge_attr_shape"] = {graph.nEdges, kEdgeFeatureCount};
797 event["logits_shape"] = {graph.nEdges};
798 event["x"] = std::move(x);
799 event["edge_index"] = std::move(edgeIndex);
800 event["edge_attr"] = std::move(edgeAttr);
801 event["edge_src"] = std::move(edgeSrc);
802 event["edge_dst"] = std::move(edgeDst);
803 event["logits"] = std::move(logits);
804 event["probabilities"] = std::move(probabilities);
805 out << event.dump() << '\n';
806
807 ATH_MSG_DEBUG("Wrote segment-edge debug event " << dumpIndex
808 << " to " << m_debugDumpFile.value());
809
810 return StatusCode::SUCCESS;
811}
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 100 of file SegmentEdgeClassifierTool.cxx.

100 {
101 if (m_sectorModulo.value() > 0 &&
102 2ULL * static_cast<unsigned long long>(m_maxDeltaSector.value()) + 1ULL >
103 static_cast<unsigned long long>(m_sectorModulo.value())) {
104 ATH_MSG_ERROR("MaxDeltaSector=" << m_maxDeltaSector.value()
105 << " spans duplicate sectors for SectorModulo="
106 << m_sectorModulo.value());
107 return StatusCode::FAILURE;
108 }
110
111 // Resolve node feature names from model metadata, matching the ONNX exporter.
112 {
113 Ort::AllocatorWithDefaultOptions allocator;
114 Ort::ModelMetadata meta = model().GetModelMetadata();
115 auto keys = meta.GetCustomMetadataMapKeysAllocated(allocator);
116 std::vector<std::string> keyList;
117 keyList.reserve(keys.size());
118 for (const auto& k : keys) keyList.emplace_back(k.get());
119
120 constexpr std::array<std::string_view, 4> candidates{
121 "x_feature_names", "node_feature_names", "feature_names", "input_feature_names"};
122 std::string usedKey;
123 std::vector<std::string> names;
124 for (std::string_view key : candidates) {
125 const std::string keyStr{key};
126 if (std::find(keyList.begin(), keyList.end(), keyStr) == keyList.end()) continue;
127 names = parseFeatureNames(meta.LookupCustomMetadataMapAllocated(keyStr.c_str(), allocator).get());
128 if (!names.empty()) {
129 usedKey = keyStr;
130 break;
131 }
132 }
133
134 if (names.empty()) {
136 ATH_MSG_WARNING("Model metadata has no usable node feature name key"
137 " (tried x_feature_names/node_feature_names/feature_names/input_feature_names)."
138 " Falling back to default training order.");
139 } else {
140 if (names.size() != kNodeFeatureCount) {
141 ATH_MSG_ERROR("Model metadata key '" << usedKey << "' has " << names.size()
142 << " features, expected " << kNodeFeatureCount);
143 return StatusCode::FAILURE;
144 }
145 for (const std::string& n : names) {
146 if (!nodeFeatureIdFromName(n).has_value()) {
147 ATH_MSG_ERROR("Unsupported node feature name in model metadata ('" << usedKey
148 << "'): '" << n << "'."
149 " Add mapping in SegmentEdgeClassifierTool::nodeFeatureValue().");
150 return StatusCode::FAILURE;
151 }
152 }
153 m_nodeFeatureNames = std::move(names);
154 ATH_MSG_DEBUG("Using node feature names from model metadata key '" << usedKey << "'.");
155 }
156
157 m_nodeFeatureIds.reserve(m_nodeFeatureNames.size());
158 for (const std::string& n : m_nodeFeatureNames) {
159 const auto id = nodeFeatureIdFromName(n);
160 if (!id.has_value()) {
161 ATH_MSG_ERROR("Internal feature-id resolution failed for node feature name '" << n << "'.");
162 return StatusCode::FAILURE;
163 }
164 m_nodeFeatureIds.push_back(*id);
165 }
166
167 std::ostringstream order;
168 order << "Node feature order:";
169 for (std::size_t i = 0; i < m_nodeFeatureNames.size(); ++i) {
170 order << " f" << i << "=" << m_nodeFeatureNames[i];
171 if (i + 1 < m_nodeFeatureNames.size()) order << ",";
172 }
173 ATH_MSG_DEBUG(order.str());
174 }
175
177 ATH_MSG_ERROR("Internal node feature setup has " << m_nodeFeatureNames.size()
178 << " entries, expected " << kNodeFeatureCount);
179 return StatusCode::FAILURE;
180 }
181 if (m_nodeFeatureIds.size() != kNodeFeatureCount) {
182 ATH_MSG_ERROR("Internal node feature id setup has " << m_nodeFeatureIds.size()
183 << " entries, expected " << kNodeFeatureCount);
184 return StatusCode::FAILURE;
185 }
186
187 m_cosMin = std::cos(m_maxDeltaThetaDeg.value() * Gaudi::Units::deg);
188
189 if (!m_debugDumpFile.value().empty()) {
190 std::ofstream out{m_debugDumpFile.value(), std::ios::out | std::ios::trunc};
191 if (!out) {
192 ATH_MSG_ERROR("Could not create segment-edge debug dump file: "
193 << m_debugDumpFile.value());
194 return StatusCode::FAILURE;
195 }
196
197 nlohmann::ordered_json metadata;
198 metadata["record_type"] = "metadata";
199 metadata["format_version"] = 1;
200 metadata["tool"] = "SegmentEdgeClassifierTool";
201 metadata["input_names"] = {m_inputNodeName.value(),
202 m_inputEdgeIndexName.value(),
203 m_inputEdgeAttrName.value()};
204 metadata["output_name"] = m_outputName.value();
205 metadata["x_feature_names"] = m_nodeFeatureNames;
206 metadata["edge_attr_feature_names"] = {
207 "deltaPositionX_m", "deltaPositionY_m", "deltaPositionZ_m",
208 "distance_m", "cos_opening_angle", "same_chamber", "same_sector"};
209 metadata["edge_index_layout"] = "row_major_2_by_E";
210 metadata["edge_order"] = "directed src_to_dst; row 0 then row 1";
211 metadata["max_delta_theta_deg"] = m_maxDeltaThetaDeg.value();
212 metadata["max_delta_sector"] = m_maxDeltaSector.value();
213 metadata["sector_modulo"] = m_sectorModulo.value();
214 metadata["debug_dump_max_events"] = m_debugDumpMaxEvents.value();
215 out << metadata.dump() << '\n';
216
217 ATH_MSG_INFO("Writing segment-edge ONNX debug dump to "
218 << m_debugDumpFile.value()
219 << " (DebugDumpMaxEvents="
220 << m_debugDumpMaxEvents.value() << ")");
221 }
222
223 return StatusCode::SUCCESS;
224}
#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 226 of file SegmentEdgeClassifierTool.cxx.

226 {
227 ATH_MSG_ERROR("runGraphInference is not supported by SegmentEdgeClassifierTool. Use SegmentEdgeInferenceAlg + ISegmentEdgeClassifierTool methods.");
228 return StatusCode::FAILURE;
229}

◆ 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 112 of file SegmentEdgeClassifierTool.h.

112{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 118 of file SegmentEdgeClassifierTool.h.

118{0};

◆ m_debugDumpFile

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

Definition at line 110 of file SegmentEdgeClassifierTool.h.

110{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 111 of file SegmentEdgeClassifierTool.h.

111{this, "DebugDumpMaxEvents", 0};

◆ m_debugDumpMutex

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

Definition at line 117 of file SegmentEdgeClassifierTool.h.

◆ m_dropIsolatedNodesBeforeInference

Gaudi::Property<bool> MuonML::SegmentEdgeClassifierTool::m_dropIsolatedNodesBeforeInference
private
Initial value:
{this, "DropIsolatedNodesBeforeInference", true,
"Remove nodes without a retained pre-ONNX edge before creating ONNX tensors"}

Definition at line 104 of file SegmentEdgeClassifierTool.h.

104 {this, "DropIsolatedNodesBeforeInference", true,
105 "Remove nodes without a retained pre-ONNX edge before creating ONNX tensors"};

◆ m_dropSameChamberEdgesBeforeInference

Gaudi::Property<bool> MuonML::SegmentEdgeClassifierTool::m_dropSameChamberEdgesBeforeInference
private
Initial value:
{this, "DropSameChamberEdgesBeforeInference", true,
"Drop same-chamber segment pairs before ONNX inference"}

Definition at line 102 of file SegmentEdgeClassifierTool.h.

102 {this, "DropSameChamberEdgesBeforeInference", true,
103 "Drop same-chamber segment pairs before ONNX inference"};

◆ 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 108 of file SegmentEdgeClassifierTool.h.

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

◆ m_inputEdgeIndexName

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

Definition at line 107 of file SegmentEdgeClassifierTool.h.

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

◆ m_inputNodeName

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

Definition at line 106 of file SegmentEdgeClassifierTool.h.

106{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_maxEdgesPerNodeBeforeInference

Gaudi::Property<unsigned int> MuonML::SegmentEdgeClassifierTool::m_maxEdgesPerNodeBeforeInference
private
Initial value:
{this, "MaxEdgesPerNodeBeforeInference", 0,
"Keep at most this many geometrical neighbour pairs per node before ONNX inference; 0 keeps all"}

Definition at line 97 of file SegmentEdgeClassifierTool.h.

97 {this, "MaxEdgesPerNodeBeforeInference", 0,
98 "Keep at most this many geometrical neighbour pairs per node before ONNX inference; 0 keeps all"};

◆ m_maxEdgesPerTargetChamberBeforeInference

Gaudi::Property<unsigned int> MuonML::SegmentEdgeClassifierTool::m_maxEdgesPerTargetChamberBeforeInference
private
Initial value:
{
this, "MaxEdgesPerTargetChamberBeforeInference", 0,
"Keep at most this many pre-ONNX neighbours from one target chamber per node; 0 keeps all"}

Definition at line 99 of file SegmentEdgeClassifierTool.h.

99 {
100 this, "MaxEdgesPerTargetChamberBeforeInference", 0,
101 "Keep at most this many pre-ONNX neighbours from one target chamber per node; 0 keeps all"};

◆ 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_maxSegmentsPerBucket

Gaudi::Property<unsigned int> MuonML::SegmentEdgeClassifierTool::m_maxSegmentsPerBucket
private
Initial value:
{this, "MaxSegmentsPerBucket", 0,
"Keep at most this many quality-ranked segments per (sector, chamber, eta) bucket before inference; 0 keeps all"}

Definition at line 95 of file SegmentEdgeClassifierTool.h.

95 {this, "MaxSegmentsPerBucket", 0,
96 "Keep at most this many quality-ranked segments per (sector, chamber, eta) bucket before inference; 0 keeps all"};

◆ 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 116 of file SegmentEdgeClassifierTool.h.

116{};

◆ 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 115 of file SegmentEdgeClassifierTool.h.

115{};

◆ 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 109 of file SegmentEdgeClassifierTool.h.

109{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
private
Initial value:
{this, "SectorModulo", 16,
"Number of muon sectors used when applying wrap-around sector distance"}

Definition at line 93 of file SegmentEdgeClassifierTool.h.

93 {this, "SectorModulo", 16,
94 "Number of muon sectors used when applying wrap-around sector distance"};

◆ 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: