ATLAS Offline Software
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 
10 namespace IDTPM {
11 
12 // =====================================================================
13 // matchVectors
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
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 // =====================================================================
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
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 // =====================================================================
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
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 // =====================================================================
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 {
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