3 // =====================================================================
4 // File: StableMatchingBase.icc
5 // Purpose: Implementation of template base class for stable matching
6 // =====================================================================
12 // =====================================================================
14 // =====================================================================
15 template <typename T, typename R>
16 StatusCode StableMatchingBase<T, R>::matchVectors(
17 const std::vector<const T*>& vTest,
18 const std::vector<const R*>& vRef,
19 ITrackMatchingLookup& matches) const
21 ATH_MSG_DEBUG("matchVectors called");
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;
30 // build distance matrix between tests and references
31 std::vector<std::vector<float>> dist;
32 buildDistanceMatrix(vTest, vRef, dist);
34 // build test preference lists and reference rankings lists
35 std::vector<std::vector<int>> testPrefs, refRankings;
36 buildPreferenceLists(dist, testPrefs, refRankings);
38 // stable matching using Gale-Shapely algorithm
39 std::vector<int> testMatch(nT, -1), refMatch(nR, -1);
40 galeShapley(testPrefs, refRankings, dist, testMatch, refMatch);
42 // Insert matches into lookup structure
43 for (size_t i = 0; i < nT; ++i) {
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 ) );
52 ATH_MSG_DEBUG("Stable matching complete.");
53 return StatusCode::SUCCESS;
56 // =====================================================================
57 // buildDistanceMatrix
58 // =====================================================================
59 template <typename T, typename R>
60 void 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
65 const size_t nT = vTest.size();
66 const size_t nR = vRef.size();
68 dist.assign(nT, std::vector<float>(nR, -1));
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]);
77 // =====================================================================
78 // buildPreferenceLists
79 // =====================================================================
80 template <typename T, typename R>
81 void 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
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
89 testPrefs.assign(nT, std::vector<int>());
90 for (size_t i = 0; i < nT; ++i) {
91 std::vector<int> order;
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) {
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);
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;
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));
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);
126 // =====================================================================
128 // =====================================================================
129 template <typename T, typename R>
130 void 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 {
137 const size_t nT = testPrefs.size();
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));
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();
150 size_t i = static_cast<size_t>(ti);
152 // Skip if no more refs to propose to, test will remain unmatched
153 if (nextProposalIdx[i] >= testPrefs[i].size()) continue;
155 // Fetch the next preferred reference for this test rj, then increment its proposal index
156 int rj = testPrefs[i][ nextProposalIdx[i]++ ];
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
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]);
168 if (rankNew < rankCurr) {
169 // ref prefers new proposer so assign
170 refMatch[rj] = static_cast<int>(i);
172 testMatch[currentT] = -1;
173 // replaced test rejoins queue if refs remain
174 if (nextProposalIdx[currentT] < testPrefs[currentT].size()) freeQueue.push(currentT);
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);
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
183 } // end of Gale-Shapley method