ATLAS Offline Software
Loading...
Searching...
No Matches
SegmentTrackCandidateBuilderTool.cxx
Go to the documentation of this file.
2#include <algorithm>
3#include <cstdint>
4#include <numeric>
5#include <set>
6#include <unordered_map>
7
8namespace MuonML {
9namespace {
10
11// Node index container reused by candidate and component builders.
12using NodeVec = std::vector<std::size_t>;
13
14// Disjoint-set union for connected-component clustering.
15struct DSU {
16 NodeVec p;
17 explicit DSU(std::size_t n) : p(n) { std::iota(p.begin(), p.end(), 0); }
18 // Terminates because roots satisfy p[root] == root; path compression shortens chains.
19 std::size_t find(std::size_t x) { return p[x] == x ? x : p[x] = find(p[x]); }
20 void unite(std::size_t a, std::size_t b) {
21 a = find(a);
22 b = find(b);
23 if (a != b) p[b] = a;
24 }
25};
26
27void addUnique(std::vector<unsigned>& ids, unsigned id) {
28 if (std::find(ids.begin(), ids.end(), id) == ids.end()) ids.push_back(id);
29}
30
31std::uint64_t pairKey(std::size_t a, std::size_t b) {
32 if (a > b) std::swap(a, b);
33 return (static_cast<std::uint64_t>(a) << 32) | static_cast<std::uint64_t>(b);
34}
35
36std::pair<std::size_t, std::size_t> unpackPairKey(std::uint64_t key) {
37 return {static_cast<std::size_t>(key >> 32),
38 static_cast<std::size_t>(key & 0xffffffffu)};
39}
40
41std::unordered_map<std::uint64_t, float>
42makeUndirectedScores(const SegmentEdgeGraph& graph,
43 const std::vector<SegmentEdgeScore>& scores,
44 const bool symmetrize) {
45 std::unordered_map<std::uint64_t, float> pairScore;
46 pairScore.reserve(scores.size());
47 for (const SegmentEdgeScore& s : scores) {
48 if (s.src >= graph.nNodes || s.dst >= graph.nNodes || s.src == s.dst) continue;
49 const std::uint64_t key = symmetrize ? pairKey(s.src, s.dst)
50 : ((static_cast<std::uint64_t>(s.src) << 32) |
51 static_cast<std::uint64_t>(s.dst));
52 auto [it, inserted] = pairScore.emplace(key, s.probability);
53 if (!inserted) it->second = std::max(it->second, s.probability);
54 }
55 return pairScore;
56}
57
58std::vector<NodeVec> componentsAboveThreshold(const SegmentEdgeGraph& graph,
59 const std::unordered_map<std::uint64_t, float>& pairScore,
60 const float threshold,
61 const bool keepIsolated) {
62 DSU dsu(graph.nNodes);
63 std::vector<char> touched(graph.nNodes, false);
64
65 for (const auto& [key, prob] : pairScore) {
66 if (prob < threshold) continue;
67 const auto [a, b] = unpackPairKey(key);
68 if (a >= graph.nNodes || b >= graph.nNodes) continue;
69 dsu.unite(a, b);
70 touched[a] = true;
71 touched[b] = true;
72 }
73
74 std::unordered_map<std::size_t, NodeVec> byRoot;
75 byRoot.reserve(graph.nNodes);
76 for (std::size_t i = 0; i < graph.nNodes; ++i) {
77 if (!touched[i] && !keepIsolated) continue;
78 byRoot[dsu.find(i)].push_back(i);
79 }
80
81 std::vector<NodeVec> out;
82 out.reserve(byRoot.size());
83 for (auto& [_, nodes] : byRoot) {
84 std::ranges::sort(nodes);
85 out.push_back(std::move(nodes));
86 }
87 return out;
88}
89}
90
92 const EventContext&,
93 const SegmentEdgeGraph& graph,
94 const std::vector<SegmentEdgeScore>& scores,
95 std::vector<std::vector<unsigned>>& candidateIdsPerSegment) const {
96
97 candidateIdsPerSegment.assign(graph.nNodes, {});
98
99 if (graph.nNodes == 0) return StatusCode::SUCCESS;
100
101 const auto pairScore = makeUndirectedScores(graph, scores, m_symmetrizeDirectedEdges.value());
102
103 // 1. High-purity cores.
104 std::vector<NodeVec> candidates =
105 componentsAboveThreshold(graph, pairScore,
106 m_overlapThreshold.value(),
107 m_keepIsolatedSegments.value());
108
109 // 2. Recall recovery components.
110 //
111 // This is the important no-loss improvement. A true trajectory may contain
112 // only 0.3--0.7 edge probabilities, especially for difficult segments.
113 // The previous implementation never created a candidate unless some edge
114 // crossed OverlapThreshold. Here we add an extra low-threshold connected
115 // component candidate.
116 if (m_useRecoveryComponents.value()) {
117 std::vector<NodeVec> recovery =
118 componentsAboveThreshold(graph, pairScore,
119 m_edgeThreshold.value(),
120 m_keepIsolatedSegments.value());
121 candidates.insert(candidates.end(),
122 std::make_move_iterator(recovery.begin()),
123 std::make_move_iterator(recovery.end()));
124 }
125
126 // 3. Optional validation/debug safety net.
127 //
128 // This should be disabled for production timing studies, but it is useful
129 // when proving that remaining losses are due to the model/graph pruning
130 // rather than the seeding wrapper.
132 graph.nNodes >= m_minCandidateSize.value()) {
133 NodeVec all(graph.nNodes);
134 std::iota(all.begin(), all.end(), 0);
135 candidates.push_back(std::move(all));
136 }
137
138 // 4. Remove exact duplicate candidate node sets and assign IDs.
139 std::set<NodeVec> seen;
140 unsigned next = 0;
141 for (NodeVec& nodes : candidates) {
142 std::sort(nodes.begin(), nodes.end());
143 nodes.erase(std::unique(nodes.begin(), nodes.end()), nodes.end());
144 if (nodes.size() < m_minCandidateSize.value()) continue;
145 if (!seen.insert(nodes).second) continue;
146
147 for (std::size_t n : nodes) {
148 addUnique(candidateIdsPerSegment[n], next);
149 }
150 ++next;
151 }
152
153 for (auto& ids : candidateIdsPerSegment) {
154 std::sort(ids.begin(), ids.end());
155 }
156
157 std::unordered_map<unsigned, unsigned> candSegCount;
158 candSegCount.reserve(next);
159 for (const auto& ids : candidateIdsPerSegment) {
160 for (unsigned id : ids) ++candSegCount[id];
161 }
162
163 std::size_t assignedNodes = 0;
164 std::size_t multiAssignedNodes = 0;
165 for (const auto& ids : candidateIdsPerSegment) {
166 assignedNodes += !ids.empty();
167 multiAssignedNodes += ids.size() > 1;
168 }
169
170 ATH_MSG_DEBUG("buildCandidates: " << candSegCount.size()
171 << " candidate(s) from " << graph.nNodes
172 << " segment(s); assignedNodes=" << assignedNodes
173 << ", multiAssignedNodes=" << multiAssignedNodes
174 << ", edgeThreshold=" << m_edgeThreshold.value()
175 << ", overlapThreshold=" << m_overlapThreshold.value()
176 << ", recovery=" << m_useRecoveryComponents.value()
177 << ", symmetrize=" << m_symmetrizeDirectedEdges.value());
178 if (msgLvl(MSG::VERBOSE)) {
179 for (const auto& [id, nSegs] : candSegCount) {
180 ATH_MSG_VERBOSE(" candidate " << id << ": " << nSegs << " segment(s)");
181 }
182 }
183
184 return StatusCode::SUCCESS;
185}
186} // namespace MuonML
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_DEBUG(x)
static Double_t a
if(pathvar)
#define x
StatusCode buildCandidates(const EventContext &ctx, const SegmentEdgeGraph &graph, const std::vector< SegmentEdgeScore > &scores, std::vector< std::vector< unsigned > > &candidateIdsPerSegment) const override
std::string find(const std::string &s)
return a remapped string
Definition hcg.cxx:140
FastGraph::NodeVec NodeVec
DataModel_detail::iterator< DVL > unique(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of unique for DataVector/List.
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.
void swap(ElementLinkVector< DOBJ > &lhs, ElementLinkVector< DOBJ > &rhs)