Visual Servoing Platform  version 3.6.1 under development (2024-12-17)
catchMunkres.cpp
1 /*
2  * ViSP, open source Visual Servoing Platform software.
3  * Copyright (C) 2005 - 2023 by Inria. All rights reserved.
4  *
5  * This software is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * See the file LICENSE.txt at the root directory of this source
10  * distribution for additional information about the GNU GPL.
11  *
12  * For using ViSP with software that can not be combined with the GNU
13  * GPL, please contact Inria about acquiring a ViSP Professional
14  * Edition License.
15  *
16  * See https://visp.inria.fr for more information.
17  *
18  * This software was developed at:
19  * Inria Rennes - Bretagne Atlantique
20  * Campus Universitaire de Beaulieu
21  * 35042 Rennes Cedex
22  * France
23  *
24  * If you have questions regarding the use of this file, please contact
25  * Inria at visp@inria.fr
26  *
27  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29  *
30  * Description:
31  * Test Munkres assignment algorithm.
32  */
39 #include <visp3/core/vpMunkres.h>
40 
41 // Check if std:c++17 or higher
42 #if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L)))
43 
44 // System
45 #include <iostream>
46 
47 #ifndef DOXYGEN_SHOULD_SKIP_THIS
48 namespace std
49 {
50 
51 // Helper to output a Munkres pair
52 ostream &operator<<(ostream &os, const pair<unsigned int, unsigned int> &val)
53 {
54  os << "[" << val.first << "," << val.second << "]";
55  return os;
56 }
57 } // namespace std
58 #endif // DOXYGEN_SHOULD_SKIP_THIS
59 
60 #if defined(VISP_HAVE_CATCH2)
61 
62 #include <catch_amalgamated.hpp>
63 
64 #ifdef ENABLE_VISP_NAMESPACE
65 using namespace VISP_NAMESPACE_NAME;
66 #endif
67 
68 TEST_CASE("Check Munkres-based assignment", "[visp_munkres]")
69 {
70  auto testMunkres = [](const std::vector<std::vector<double> > &cost_matrix,
71  const std::vector<std::pair<unsigned int, unsigned int> > &expected_pairs) {
72  const auto munkres_pairs = vpMunkres::run(cost_matrix);
73  REQUIRE(expected_pairs.size() == munkres_pairs.size());
74  for (auto i = 0u; i < munkres_pairs.size(); i++) {
75  REQUIRE(expected_pairs.at(i) == munkres_pairs.at(i));
76  }
77  };
78 
79  SECTION("Square cost matrix")
80  {
81  std::vector<std::vector<double> > costs {};
82  costs.push_back(std::vector<double>{3, 1, 2});
83  costs.push_back(std::vector<double>{2, 3, 1});
84  costs.push_back(std::vector<double>{1, 2, 3});
85 
86  std::vector<std::pair<unsigned int, unsigned int> > expected_pairs {};
87  expected_pairs.emplace_back(0, 1);
88  expected_pairs.emplace_back(1, 2);
89  expected_pairs.emplace_back(2, 0);
90 
91  testMunkres(costs, expected_pairs);
92  }
93 
94  SECTION("Horizontal cost matrix")
95  {
96  std::vector<std::vector<double> > costs {};
97  costs.push_back(std::vector<double>{4, 1, 2, 3});
98  costs.push_back(std::vector<double>{3, 4, 1, 2});
99  costs.push_back(std::vector<double>{2, 3, 4, 1});
100 
101  std::vector<std::pair<unsigned int, unsigned int> > expected_pairs {};
102  expected_pairs.emplace_back(0, 1);
103  expected_pairs.emplace_back(1, 2);
104  expected_pairs.emplace_back(2, 3);
105 
106  testMunkres(costs, expected_pairs);
107  }
108 
109  SECTION("Vertical cost matrix")
110  {
111  std::vector<std::vector<double> > costs {};
112  costs.push_back(std::vector<double>{4, 1, 2});
113  costs.push_back(std::vector<double>{3, 4, 1});
114  costs.push_back(std::vector<double>{2, 3, 4});
115  costs.push_back(std::vector<double>{1, 2, 3});
116 
117  std::vector<std::pair<unsigned int, unsigned int> > expected_pairs {};
118  expected_pairs.emplace_back(0, 1);
119  expected_pairs.emplace_back(1, 2);
120  expected_pairs.emplace_back(3, 0);
121 
122  testMunkres(costs, expected_pairs);
123  }
124 }
125 
126 int main(int argc, char *argv[])
127 {
128  Catch::Session session;
129  session.applyCommandLine(argc, argv);
130  int numFailed = session.run();
131  return numFailed;
132 }
133 
134 #else
135 // Fallback to classic tests
136 
137 bool testMunkres(const std::vector<std::vector<double> > &costs_matrix,
138  const std::vector<std::pair<unsigned int, unsigned int> > &expected_pairs)
139 {
140  const auto pairs = vpMunkres::run(costs_matrix);
141 
142  if (pairs.size() != expected_pairs.size()) {
143  // clang-format off
144  std::cerr << "Expected nb of association | Munkres nb of association: "
145  << expected_pairs.size() << " | " << pairs.size()
146  << std::endl;
147  // clang-format on
148  return false;
149  }
150 
151  for (auto i = 0u; i < pairs.size(); i++) {
152  if (expected_pairs.at(i) != pairs.at(i)) {
153 
154  // Output the cost matrix
155  std::cout << "Cost matrix:" << std::endl;
156  for (const auto &cost_row : costs_matrix) {
157  std::cout << "| ";
158  for (const auto &cost : cost_row) {
159  std::cout << cost << " | ";
160  }
161  std::cout << std::endl;
162  }
163  std::cout << std::endl;
164 
165  // Output the pair which fails
166  std::cerr << "FAIL: "
167  << "Expected association | Munkres association: " << expected_pairs.at(i) << " | " << pairs.at(i)
168  << std::endl;
169 
170  return false;
171  }
172  }
173 
174  return true;
175 }
176 
177 bool testSquareMat()
178 {
179  std::vector<std::vector<double> > costs {};
180  costs.push_back(std::vector<double>{3, 4, 1, 2});
181  costs.push_back(std::vector<double>{3, 4, 2, 1});
182  costs.push_back(std::vector<double>{1, 2, 3, 4});
183  costs.push_back(std::vector<double>{2, 1, 4, 3});
184 
185  std::vector<std::pair<unsigned int, unsigned int> > pairs {};
186  pairs.emplace_back(0, 2);
187  pairs.emplace_back(1, 3);
188  pairs.emplace_back(2, 0);
189  pairs.emplace_back(3, 1);
190 
191  return testMunkres(costs, pairs);
192 }
193 
194 bool testVertMat()
195 {
196  std::vector<std::vector<double> > costs {};
197  costs.push_back(std::vector<double>{3, 2, 1});
198  costs.push_back(std::vector<double>{4, 3, 2});
199  costs.push_back(std::vector<double>{1, 4, 3});
200  costs.push_back(std::vector<double>{2, 1, 4});
201 
202  std::vector<std::pair<unsigned int, unsigned int> > pairs {};
203  pairs.emplace_back(0, 2);
204  pairs.emplace_back(2, 0);
205  pairs.emplace_back(3, 1);
206 
207  return testMunkres(costs, pairs);
208 }
209 
210 bool testHorMat()
211 {
212  std::vector<std::vector<double> > costs {};
213  costs.push_back(std::vector<double>{2, 3, 4, 1});
214  costs.push_back(std::vector<double>{4, 1, 2, 3});
215  costs.push_back(std::vector<double>{1, 2, 3, 4});
216 
217  std::vector<std::pair<unsigned int, unsigned int> > pairs {};
218  pairs.emplace_back(0, 3);
219  pairs.emplace_back(1, 1);
220  pairs.emplace_back(2, 0);
221 
222  return testMunkres(costs, pairs);
223 }
224 
225 int main()
226 {
227  if (not testSquareMat()) {
228  return EXIT_FAILURE;
229  }
230 
231  if (not testVertMat()) {
232  return EXIT_FAILURE;
233  }
234 
235  if (not testHorMat()) {
236  return EXIT_FAILURE;
237  }
238 
239  return EXIT_SUCCESS;
240 }
241 #endif
242 
243 #else
244 int main() { return EXIT_SUCCESS; }
245 #endif