ATLAS Offline Software
Loading...
Searching...
No Matches
StableMatchingBase.icc
Go to the documentation of this file.
1// -*- mode: c++; -*-
2
3// =====================================================================
4// File: StableMatchingBase.icc
5// Purpose: Implementation of template base class for stable matching
6// =====================================================================
7
8#include <queue>
9
10namespace IDTPM {
11
12// =====================================================================
13// matchVectors
14// =====================================================================
15template <typename T, typename R>
16StatusCode StableMatchingBase<T, R>::matchVectors(
17 const std::vector<const T*>& vTest,
18 const std::vector<const R*>& vRef,
19 ITrackMatchingLookup& matches) const
20{
21 ATH_MSG_DEBUG("matchVectors called");
22
23 const size_t nT = vTest.size();
24 const size_t nR = vRef.size();
25 if (nT == 0 || nR == 0) {
26 ATH_MSG_DEBUG("Empty input vectors — no matches to compute");
27 return StatusCode::SUCCESS;
28 }
29
30 // build distance matrix between tests and references
31 std::vector<std::vector<float>> dist;
32 buildDistanceMatrix(vTest, vRef, dist);
33
34 // build test preference lists and reference rankings lists
35 std::vector<std::vector<int>> testPrefs, refRankings;
36 buildPreferenceLists(dist, testPrefs, refRankings);
37
38 // stable matching using Gale-Shapely algorithm
39 std::vector<int> testMatch(nT, -1), refMatch(nR, -1);
40 galeShapley(testPrefs, refRankings, dist, testMatch, refMatch);
41
42 // Insert matches into lookup structure
43 for (size_t i = 0; i < nT; ++i) {
44 int j = testMatch[i];
45 if (j >= 0) {
46 const T* tptr = vTest[i];
47 const R* rptr = vRef[j];
48 float dval = dist[i][j];
49 ATH_CHECK( matches.update( *tptr, *rptr, dval ) );
50 }
51 }
52 ATH_MSG_DEBUG("Stable matching complete.");
53 return StatusCode::SUCCESS;
54}
55
56// =====================================================================
57// buildDistanceMatrix
58// =====================================================================
59template <typename T, typename R>
60void StableMatchingBase<T, R>::buildDistanceMatrix(
61 const std::vector<const T*>& vTest,
62 const std::vector<const R*>& vRef,
63 std::vector<std::vector<float>>& dist) const
64{
65 const size_t nT = vTest.size();
66 const size_t nR = vRef.size();
67
68 dist.assign(nT, std::vector<float>(nR, -1));
69
70 for (size_t i = 0; i < nT; ++i) {
71 for (size_t j = 0; j < nR; ++j) {
72 dist[i][j] = distance(*vTest[i], *vRef[j]);
73 }
74 }
75}
76
77// =====================================================================
78// buildPreferenceLists
79// =====================================================================
80template <typename T, typename R>
81void StableMatchingBase<T, R>::buildPreferenceLists(
82 const std::vector<std::vector<float>>& dist,
83 std::vector<std::vector<int>>& testPrefs,
84 std::vector<std::vector<int>>& refRankings) const
85{
86 const size_t nT = dist.size();
87 const size_t nR = (nT ? dist[0].size() : 0); // guard against segfault if dist[0] doesnt exist
88
89 testPrefs.assign(nT, std::vector<int>());
90 for (size_t i = 0; i < nT; ++i) {
91 std::vector<int> order;
92 order.reserve(nR);
93 // Only include references with valid distances (filter out -1)
94 for (size_t j = 0; j < nR; ++j) {
95 if (dist[i][j] >= 0) {
96 order.push_back(j);
97 }
98 }
99 // Always sort by distance to ensure distance-based preferences
100 std::stable_sort(order.begin(), order.end(),
101 [&](int a, int b) { return dist[i][a] < dist[i][b]; });
102 testPrefs[i] = std::move(order);
103 }
104
105 // Build refRankings as test_index → rank mapping (lower rank = more preferred)
106 refRankings.assign(nR, std::vector<int>(nT, nT)); // Default to max rank (nT) for unmatched/invalid
107 for (size_t j = 0; j < nR; ++j) {
108 std::vector<std::pair<float, int>> scores;
109 scores.reserve(nT);
110 // Only include tests with valid distances (filter out -1)
111 for (size_t i = 0; i < nT; ++i) {
112 if (dist[i][j] >= 0) {
113 scores.emplace_back(dist[i][j], static_cast<int>(i));
114 }
115 }
116 // Always sort by distance to ensure distance-based preferences
117 std::sort(scores.begin(), scores.end(),
118 [](auto& a, auto& b) { return a.first < b.first; });
119 for (size_t rank = 0; rank < scores.size(); ++rank) {
120 int testIdx = scores[rank].second;
121 refRankings[j][testIdx] = static_cast<int>(rank);
122 }
123 }
124}
125
126// =====================================================================
127// galeShapley
128// =====================================================================
129template <typename T, typename R>
130void StableMatchingBase<T, R>::galeShapley(
131 const std::vector<std::vector<int>>& testPrefs,
132 const std::vector<std::vector<int>>& refRankings,
133 const std::vector<std::vector<float>>& /*dist*/,
134 std::vector<int>& testMatch,
135 std::vector<int>& refMatch) const {
136
137 const size_t nT = testPrefs.size();
138
139 // Store the index of the next reference to propose to and the index of tests that need a match
140 std::vector<size_t> nextProposalIdx(nT, 0);
141 std::queue<int> freeQueue;
142 for (size_t i = 0; i < nT; ++i) {
143 if (!testPrefs[i].empty()) freeQueue.push(static_cast<int>(i));
144 }
145
146 // Main loop - take the next free test, propose to next ref, loop until free tests dont have any refs to propose to
147 while (!freeQueue.empty()) {
148 int ti = freeQueue.front();
149 freeQueue.pop();
150 size_t i = static_cast<size_t>(ti);
151
152 // Skip if no more refs to propose to, test will remain unmatched
153 if (nextProposalIdx[i] >= testPrefs[i].size()) continue;
154
155 // Fetch the next preferred reference for this test rj, then increment its proposal index
156 int rj = testPrefs[i][ nextProposalIdx[i]++ ];
157
158 // If that reference is free, accept the proposal
159 if (refMatch[rj] == -1) {
160 refMatch[rj] = static_cast<int>(i); // ref rj is now matched to test i
161 testMatch[i] = rj; // test i is matched to ref rj
162 } else {
163 // If reference is already matched then check preference
164 int currentT = refMatch[rj];
165 int rankNew = (refRankings[rj][i]);
166 int rankCurr = (refRankings[rj][currentT]);
167
168 if (rankNew < rankCurr) {
169 // ref prefers new proposer so assign
170 refMatch[rj] = static_cast<int>(i);
171 testMatch[i] = rj;
172 testMatch[currentT] = -1;
173 // replaced test rejoins queue if refs remain
174 if (nextProposalIdx[currentT] < testPrefs[currentT].size()) freeQueue.push(currentT);
175 } else {
176 // ref prefers current match so rejects i; test remains free if it has more refs
177 if (nextProposalIdx[i] < testPrefs[i].size()) freeQueue.push(ti);
178 }
179 }
180 // iterate until queue is empty and all tests are matched or every unmatched test has exhausted its preference list
181 } // end of main loop
182
183} // end of Gale-Shapley method
184
185} // namespace IDTPM