Visual Servoing Platform  version 3.6.1 under development (2024-10-02)
testMunkres.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 #ifdef VISP_HAVE_CATCH2
61 #define CATCH_CONFIG_RUNNER
62 #include <catch.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 
131  return session.run();
132 }
133 #else
134 // Fallback to classic tests
135 
136 bool testMunkres(const std::vector<std::vector<double> > &costs_matrix,
137  const std::vector<std::pair<unsigned int, unsigned int> > &expected_pairs)
138 {
139  const auto pairs = vpMunkres::run(costs_matrix);
140 
141  if (pairs.size() != expected_pairs.size()) {
142  // clang-format off
143  std::cerr << "Expected nb of association | Munkres nb of association: "
144  << expected_pairs.size() << " | " << pairs.size()
145  << std::endl;
146 // clang-format on
147  return false;
148  }
149 
150  for (auto i = 0u; i < pairs.size(); i++) {
151  if (expected_pairs.at(i) != pairs.at(i)) {
152 
153  // Output the cost matrix
154  std::cout << "Cost matrix:" << std::endl;
155  for (const auto &cost_row : costs_matrix) {
156  std::cout << "| ";
157  for (const auto &cost : cost_row) {
158  std::cout << cost << " | ";
159  }
160  std::cout << std::endl;
161  }
162  std::cout << std::endl;
163 
164  // Output the pair which fails
165  std::cerr << "FAIL: "
166  << "Expected association | Munkres association: " << expected_pairs.at(i) << " | " << pairs.at(i)
167  << std::endl;
168 
169  return false;
170  }
171  }
172 
173  return true;
174 }
175 
176 bool testSquareMat()
177 {
178  std::vector<std::vector<double> > costs {};
179  costs.push_back(std::vector<double>{3, 4, 1, 2});
180  costs.push_back(std::vector<double>{3, 4, 2, 1});
181  costs.push_back(std::vector<double>{1, 2, 3, 4});
182  costs.push_back(std::vector<double>{2, 1, 4, 3});
183 
184  std::vector<std::pair<unsigned int, unsigned int> > pairs {};
185  pairs.emplace_back(0, 2);
186  pairs.emplace_back(1, 3);
187  pairs.emplace_back(2, 0);
188  pairs.emplace_back(3, 1);
189 
190  return testMunkres(costs, pairs);
191 }
192 
193 bool testVertMat()
194 {
195  std::vector<std::vector<double> > costs {};
196  costs.push_back(std::vector<double>{3, 2, 1});
197  costs.push_back(std::vector<double>{4, 3, 2});
198  costs.push_back(std::vector<double>{1, 4, 3});
199  costs.push_back(std::vector<double>{2, 1, 4});
200 
201  std::vector<std::pair<unsigned int, unsigned int> > pairs {};
202  pairs.emplace_back(0, 2);
203  pairs.emplace_back(2, 0);
204  pairs.emplace_back(3, 1);
205 
206  return testMunkres(costs, pairs);
207 }
208 
209 bool testHorMat()
210 {
211  std::vector<std::vector<double> > costs {};
212  costs.push_back(std::vector<double>{2, 3, 4, 1});
213  costs.push_back(std::vector<double>{4, 1, 2, 3});
214  costs.push_back(std::vector<double>{1, 2, 3, 4});
215 
216  std::vector<std::pair<unsigned int, unsigned int> > pairs {};
217  pairs.emplace_back(0, 3);
218  pairs.emplace_back(1, 1);
219  pairs.emplace_back(2, 0);
220 
221  return testMunkres(costs, pairs);
222 }
223 
224 int main()
225 {
226  if (not testSquareMat()) {
227  return EXIT_FAILURE;
228  }
229 
230  if (not testVertMat()) {
231  return EXIT_FAILURE;
232  }
233 
234  if (not testHorMat()) {
235  return EXIT_FAILURE;
236  }
237 
238  return EXIT_SUCCESS;
239 }
240 #endif
241 
242 #else
243 int main() { return EXIT_SUCCESS; }
244 #endif