From f98edae416607751b907f58ef47d6c2117c23b76 Mon Sep 17 00:00:00 2001 From: Joris van Rantwijk Date: Thu, 28 Nov 2024 22:38:15 +0100 Subject: [PATCH 1/4] weighted_matching_test check input file Without this check, the test program declares all tests passed if it fails to open the input file. --- test/weighted_matching_test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/weighted_matching_test.cpp b/test/weighted_matching_test.cpp index 2baa0adcf..30042c553 100644 --- a/test/weighted_matching_test.cpp +++ b/test/weighted_matching_test.cpp @@ -139,6 +139,7 @@ Graph make_graph(typename graph_traits< Graph >::vertices_size_type num_v, int main(int, char*[]) { std::ifstream in_file("weighted_matching.dat"); + BOOST_TEST(in_file.good()); std::string line; while (std::getline(in_file, line)) { From 5328bfd452793d080ed159971fae5df285086c90 Mon Sep 17 00:00:00 2001 From: Joris van Rantwijk Date: Sun, 1 Dec 2024 12:18:18 +0100 Subject: [PATCH 2/4] Add more tests for maximum_weighted_matching A few hand-picked graphs to explore basic functionality. A few graphs that are known to trigger bugs in the current implementation of maximum_weighted_matching(). A batch of 20000 random small graphs. --- test/Jamfile.v2 | 1 + test/weighted_matching_test2.cpp | 497 +++++++++++++++++++++++++++++++ 2 files changed, 498 insertions(+) create mode 100644 test/weighted_matching_test2.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index b2656eb07..b2e29dd02 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -114,6 +114,7 @@ alias graph_test_regular : [ run king_ordering.cpp ] [ run matching_test.cpp ] [ run weighted_matching_test.cpp ] + [ run weighted_matching_test2.cpp ] [ run max_flow_test.cpp ] [ run boykov_kolmogorov_max_flow_test.cpp ] [ run cycle_ratio_tests.cpp ../build//boost_graph : $(CYCLE_RATIO_INPUT_FILE) ] diff --git a/test/weighted_matching_test2.cpp b/test/weighted_matching_test2.cpp new file mode 100644 index 000000000..1d987050d --- /dev/null +++ b/test/weighted_matching_test2.cpp @@ -0,0 +1,497 @@ +//======================================================================= +// Copyright (c) 2024 Joris van Rantwijk +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +//======================================================================= + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace boost; + + +template +using adj_vec_test_graph = adjacency_list >; + +template +using adj_list_test_graph = adjacency_list, + property >; + +template +using adj_matrix_test_graph = adjacency_matrix >; + + +template +struct edge_info +{ + unsigned int x, y; + WeightType w; +}; + + +template +struct install_vertex_index +{ + static void install(Graph& g) {} +}; + +template +struct install_vertex_index> +{ + static void install(adj_list_test_graph& g) + { + auto vrange = vertices(g); + unsigned int i = 0; + for (auto it = vrange.first; it != vrange.second; ++it, ++i) + { + put(vertex_index, g, *it, i); + } + } +}; + + +template +Graph make_graph( + unsigned int nv, + const std::vector>& edges_info) +{ + typedef property EdgeProperty; + Graph g(nv); + install_vertex_index::install(g); + for (const auto& e : edges_info) + { + add_edge(vertex(e.x, g), vertex(e.y, g), EdgeProperty(e.w), g); + } + return g; +} + + +template +using graph_edge_weight_type = typename property_traits< + typename property_map::type>::value_type; + + +template +std::pair, bool> +check_matching(const Graph& g, const MateMap& mate) +{ + graph_edge_weight_type matching_weight = 0; + unsigned int n_matched_edges = 0; + + auto edge_range = edges(g); + for (auto it = edge_range.first; it != edge_range.second; ++it) + { + if (mate[source(*it, g)] == target(*it, g)) + { + ++n_matched_edges; + matching_weight += get(edge_weight, g, *it); + } + } + + unsigned int n_matched_vertices = 0; + auto vertex_range = vertices(g); + for (auto it = vertex_range.first; it != vertex_range.second; ++it) + { + if (mate[*it] != graph_traits::null_vertex()) + { + if (mate[mate[*it]] != *it) + { + return std::make_pair(0, false); + } + ++n_matched_vertices; + } + } + + if (n_matched_vertices != 2 * n_matched_edges) + { + return std::make_pair(0, false); + } + + return std::make_pair(matching_weight, true); +} + + +template +std::string show_graph(const Graph& g) +{ + std::ostringstream ss; + auto edge_range = edges(g); + for (auto it = edge_range.first; it != edge_range.second; ++it) + { + unsigned int x = get(vertex_index, g, source(*it, g)); + unsigned int y = get(vertex_index, g, target(*it, g)); + auto w = get(edge_weight, g, *it); + if (ss.tellp() > 0) + { + ss << " "; + } + ss << "{" << x << "," << y << "," << w << "}"; + } + return ss.str(); +} + + +template +void test_matching(const Graph& g, WeightType answer) +{ + typedef typename property_map::type + vertex_index_map_t; + typedef vector_property_map< + typename graph_traits::vertex_descriptor, vertex_index_map_t> + mate_t; + + mate_t mate(num_vertices(g)); + maximum_weighted_matching(g, mate); + + WeightType weight; + bool consistent_matching; + std::tie(weight, consistent_matching) = check_matching(g, mate); + BOOST_TEST(consistent_matching); + if (! consistent_matching) + { + std::cout << std::endl + << "Inconsistent answer for graph" << std::endl + << " " << show_graph(g) << std::endl; + } + else + { + bool same_answer; + if (std::numeric_limits::is_integer) + { + same_answer = (weight == answer); + } + else + { + WeightType eps = std::numeric_limits::epsilon(); + WeightType max_error = std::max(eps, answer * eps); + same_answer = (abs(weight - answer) <= max_error); + } + + BOOST_TEST(same_answer); + if (! same_answer) + { + std::cout << std::endl + << "Wrong answer for graph" << std::endl + << " " << show_graph(g) << std::endl + << " found weight " << weight + << " while expecting " << answer << std::endl; + } + } +} + + +template +void run_test_graph( + unsigned int nv, + const std::vector>& edges_info, + WeightType answer) +{ + typedef adj_vec_test_graph vec_graph_t; + test_matching( + make_graph(nv, edges_info), + answer); + + typedef adj_list_test_graph list_graph_t; + test_matching( + make_graph(nv, edges_info), + answer); + + typedef adj_matrix_test_graph matrix_graph_t; + test_matching( + make_graph(nv, edges_info), + answer); +} + + +template +WeightType find_brute_force_answer( + unsigned int nv, + const std::vector>& edges_info) +{ + typedef adj_vec_test_graph vec_graph_t; + typedef typename graph_traits::vertex_descriptor vertex_t; + + vec_graph_t g = make_graph(nv, edges_info); + vector_property_map mate(nv); + brute_force_maximum_weighted_matching(g, mate); + + WeightType weight; + bool ok; + std::tie(weight, ok) = check_matching(g, mate); + BOOST_TEST(ok); + return weight; +} + + +void run_random_graph(unsigned int n, unsigned int m, std::mt19937& rng) +{ + std::vector> edges_info; + for (unsigned int i = 0; i < m; ++i) + { + while (true) + { + unsigned int x = std::uniform_int_distribution(0U, n - 2)(rng); + unsigned int y = std::uniform_int_distribution(x + 1, n - 1)(rng); + bool ok = true; + for (const auto& e : edges_info) + { + if ((e.x == x) && (e.y == y)) + { + ok = false; + break; + } + } + if (ok) + { + long w = std::uniform_int_distribution(0, 1000)(rng); + edges_info.push_back(edge_info{x, y, w}); + break; + } + } + } + long answer = find_brute_force_answer(n, edges_info); + run_test_graph(n, edges_info, answer); +} + + +int main(int, char*[]) +{ + // Simple test cases: + + run_test_graph(0, {}, 0); + run_test_graph(1, {}, 0); + run_test_graph(2, {}, 0); + run_test_graph(2, {{0, 1, 1}}, 1); + run_test_graph(3, {{0, 1, 10}, {1, 2, 11}}, 11); + run_test_graph(4, {{0, 1, 5}, {1, 2, 11}, {2, 3, 5}}, 11); + run_test_graph(4, {{0, 1, 5}, {1, 2, 11}, {2, 3, 7}}, 12); + + // Floating point edge weights: + + run_test_graph(4, { + {0, 1, 3.1415}, {1, 2, 2.7183}, {0, 2, 3.0}, {0, 3, 1.4142}}, + 4.1325); + + // Negative edge weights: + + run_test_graph(4, { + {0, 1, 2}, {0, 2, -2}, {1, 2, 1}, {1, 3, -1}, {2, 3, -6}}, + 2); + + // Blossoms: + + run_test_graph(4, {{0, 1, 8}, {0, 2, 9}, {1, 2, 10}, {2, 3, 7}}, 15); + + run_test_graph(6, { + {0, 1, 8}, {0, 2, 9}, {1, 2, 10}, {2, 3, 7}, {0, 5, 5}, {3, 4, 6}}, + 21); + + run_test_graph(6, { + {0, 1, 9}, {0, 2, 8}, {1, 2, 10}, {0, 3, 5}, {3, 4, 4}, {0, 5, 3}}, + 17); + + run_test_graph(6, { + {0, 1, 9}, {0, 2, 8}, {1, 2, 10}, {0, 3, 5}, {3, 4, 3}, {0, 5, 4}}, + 17); + + run_test_graph(6, { + {0, 1, 9}, {0, 2, 8}, {1, 2, 10}, {0, 3, 5}, {3, 4, 3}, {2, 5, 4}}, + 16); + + run_test_graph(6, { + {0, 1, 9}, {0, 2, 9}, {1, 2, 10}, {1, 3, 8}, {2, 4, 8}, + {3, 4, 10}, {4, 5, 6}}, + 23); + + run_test_graph(8, { + {0, 1, 10}, {0, 6, 10}, {1, 2, 12}, {2, 3, 20}, {2, 4, 20}, + {3, 4, 25}, {4, 5, 10}, {5, 6, 10}, {6, 7, 8}}, + 48); + + run_test_graph(8, { + {0, 1, 8}, {0, 2, 8}, {1, 2, 10}, {1, 3, 12}, {2, 4, 12}, + {3, 4, 14}, {3, 5, 12}, {4, 6, 12}, {5, 6, 14}, {6, 7, 12}}, + 44); + + run_test_graph(8, { + {0, 1, 23}, {0, 4, 22}, {0, 5, 15}, {1, 2, 25}, {2, 3, 22}, + {3, 4, 25}, {3, 7, 14}, {4, 6, 13}}, + 67); + + run_test_graph(8, { + {0, 1, 19}, {0, 2, 20}, {0, 7, 8}, {1, 2, 25}, {1, 3, 18}, + {2, 4, 18}, {3, 4, 13}, {3, 6, 7}, {4, 5, 7}}, + 47); + + // Somewhat tricky test cases: + + run_test_graph(10, { + {0, 1, 45}, {0, 4, 45}, {1, 2, 50}, {2, 3, 45}, {3, 4, 50}, + {0, 5, 30}, {2, 8, 35}, {3, 7, 35}, {4, 6, 26}, {8, 9, 5}}, + 146); + + run_test_graph(10, { + {0, 1, 45}, {0, 4, 45}, {1, 2, 50}, {2, 3, 45}, {3, 4, 50}, + {0, 5, 30}, {2, 8, 35}, {3, 7, 26}, {4, 6, 40}, {8, 9, 5}}, + 151); + + run_test_graph(10, { + {0, 1, 45}, {0, 4, 45}, {1, 2, 50}, {2, 3, 45}, {3, 4, 50}, + {0, 5, 30}, {2, 8, 35}, {3, 7, 28}, {4, 6, 26}, {8, 9, 5}}, + 139); + + run_test_graph(12, { + {0, 1, 45}, {0, 6, 45}, {1, 2, 50}, {2, 3, 45}, {3, 4, 95}, + {3, 5, 94}, {4, 5, 94}, {5, 6, 50}, {0, 7, 30}, {2, 10, 35}, + {4, 8, 36}, {6, 9, 26}, {10, 11, 5}}, + 241); + + run_test_graph(10, { + {0, 1, 40}, {0, 2, 40}, {1, 2, 60}, {1, 3, 55}, {2, 4, 55}, + {3, 4, 50}, {0, 7, 15}, {4, 6, 30}, {6, 5, 10}, {7, 9, 10}, + {3, 8, 30}}, + 145); + + run_test_graph(6, { + {0, 1, 2}, {0, 4, 3}, {1, 2, 7}, {1, 5, 2}, {2, 3, 9}, + {2, 5, 4}, {3, 4, 8}, {3, 5, 4}}, + 15); + + run_test_graph(6, { + {0, 1, 8}, {0, 2, 8}, {1, 2, 9}, {1, 3, 6}, {2, 4, 7}, + {3, 4, 8}, {3, 5, 5}}, + 20); + + run_test_graph(7, { + {0, 1, 7}, {0, 2, 7}, {1, 2, 9}, {0, 3, 7}, {0, 4, 7}, + {3, 4, 9}, {5, 6, 2}}, + 20); + + run_test_graph(7, { + {0, 1, 7}, {0, 4, 6}, {1, 2, 9}, {2, 3, 8}, {3, 4, 9}, + {3, 5, 1}, {4, 5, 1}}, + 18); + + run_test_graph(5, { + {0, 2, 7}, {0, 3, 3}, {1, 3, 1}, {2, 3, 5}, {2, 4, 5}}, + 8); + + run_test_graph(7, { + {0, 1, 15}, {0, 2, 10}, {0, 3, 11}, {0, 5, 17}, {1, 4, 12}, + {1, 5, 8}, {4, 6, 15}, {5, 6, 7}}, + 34); + + run_test_graph(7, { + {0, 1, 12}, {0, 3, 11}, {0, 4, 11}, {1, 3, 12}, {1, 5, 11}, + {2, 3, 14}, {2, 6, 14}, {3, 6, 11}}, + 37); + + run_test_graph(7, { + {0, 1, 19}, {0, 3, 17}, {0, 4, 19}, {1, 2, 15}, {1, 4, 21}, + {3, 5, 18}, {3, 6, 11}, {4, 5, 19}}, + 52); + + run_test_graph(6, { + {0, 1, 19}, {0, 2, 19}, {0, 3, 15}, {0, 4, 17}, {1, 2, 21}, + {3, 4, 16}, {4, 5, 10}}, + 46); + + // Triangles, unit weight: + + run_test_graph(9, { + {0, 1, 1}, {0, 2, 1}, {1, 2, 1}, {0, 3, 1}, + {3, 4, 1}, {3, 5, 1}, {4, 5, 1}, {4, 7, 1}, + {6, 7, 1}, {6, 8, 1}, {7, 8, 1}}, + 4); + + // Trigger known bugs in maximum_weighted_matching: + + // wrong answer + run_test_graph(8, { + {4, 7, 453}, {0, 4, 627}, {4, 6, 853}, {6, 7, 344}, {5, 6, 906}, + {4, 5, 689}, {2, 3, 741}, {2, 7, 746}, {3, 6, 647}, {1, 5, 385}, + {5, 7, 215}, {3, 7, 640}}, + 2405); + + // wrong answer + run_test_graph(12, { + {0, 3, 448}, {0, 4, 919}, {0, 5, 918}, {0, 9, 72}, {1, 3, 830}, + {1, 4, 687}, {1, 5, 559}, {1, 6, 580}, {1, 7, 679}, {1, 8, 627}, + {2, 4, 585}, {2, 5, 835}, {2, 6, 822}, {2, 8, 462}, {2, 11, 380}, + {3, 4, 328}, {3, 5, 860}, {3, 7, 297}, {3, 8, 590}, {3, 11, 235}, + {4, 6, 692}, {4, 10, 227}, {4, 11, 354}, {5, 6, 160}, {5, 9, 400}, + {6, 8, 410}, {6, 9, 420}, {7, 11, 924}, {9, 11, 825}, {10, 11, 790}}, + 4233); + + // assertion + run_test_graph(9, { + {4, 6, 796}, {3, 4, 553}, {0, 1, 792}, {1, 7, 1000}, {4, 5, 360}, + {5, 7, 183}, {4, 8, 694}, {0, 4, 741}, {0, 2, 483}, {5, 8, 228}, + {7, 8, 644}, {1, 3, 236}, {6, 7, 895}, {1, 6, 913}, {1, 8, 617}}, + 2593); + + // assertion + run_test_graph(10, { + {0, 6, 892}, {2, 4, 517}, {4, 7, 560}, {1, 5, 828}, {1, 7, 831}, + {6, 7, 397}, {4, 5, 43}, {5, 6, 944}, {6, 9, 215}, {5, 7, 753}, + {4, 6, 901}, {1, 2, 530}, {2, 8, 384}, {3, 4, 499}, {5, 8, 190}}, + 2674); + + // segmentation fault + run_test_graph(7, { + {5, 6, 799}, {0, 4, 601}, {0, 1, 578}, {0, 3, 373}, {4, 6, 675}, + {4, 5, 925}, {0, 5, 697}, {3, 4, 260}, {1, 3, 464}, {1, 5, 845}, + {0, 2, 176}, {2, 5, 685}}, + 1938); + + // segmentation fault + run_test_graph(8, { + {2, 7, 420}, {6, 7, 414}, {2, 3, 421}, {2, 6, 854}, {1, 6, 997}, + {4, 5, 46}, {1, 2, 467}, {2, 4, 230}, {3, 7, 555}, {0, 3, 334}, + {0, 7, 341}, {0, 6, 634}}, + 1805); + + // hang + run_test_graph(7, { + {2, 3, 837}, {1, 4, 458}, {3, 4, 291}, {5, 6, 601}, {1, 2, 202}, + {0, 4, 491}, {4, 5, 910}, {0, 1, 159}, {3, 5, 684}, {4, 6, 139}, + {0, 2, 792}, {1, 3, 232}}, + 1934); + + // hang + run_test_graph(12, { + {0, 1, 856}, {0, 4, 462}, {0, 6, 874}, {0, 7, 406}, {0, 10, 294}, + {1, 3, 936}, {1, 5, 852}, {1, 7, 501}, {1, 8, 555}, {1, 10, 41}, + {2, 3, 325}, {2, 6, 748}, {2, 9, 808}, {3, 5, 870}, {3, 7, 25}, + {3, 8, 663}, {3, 9, 897}, {4, 7, 617}, {4, 9, 435}, {4, 10, 818}, + {4, 11, 933}, {5, 6, 608}, {5, 8, 636}, {6, 9, 274}, {6, 10, 279}, + {7, 9, 705}, {7, 10, 114}, {8, 11, 602}, {9, 10, 764}, {10, 11, 413}}, + 4599); + + // Random graphs: + + std::mt19937 rng(123456); + for (int k = 0; k < 20000; ++k) + { + run_random_graph(10, 15, rng); + } + + return boost::report_errors(); +} From 139610972c6068bda1bae50e99db974c4997a989 Mon Sep 17 00:00:00 2001 From: Joris van Rantwijk Date: Thu, 5 Dec 2024 20:14:26 +0100 Subject: [PATCH 3/4] Fix uniform_int_distribution C++11 compatibility --- test/weighted_matching_test2.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/weighted_matching_test2.cpp b/test/weighted_matching_test2.cpp index 1d987050d..09fc3d365 100644 --- a/test/weighted_matching_test2.cpp +++ b/test/weighted_matching_test2.cpp @@ -247,8 +247,10 @@ void run_random_graph(unsigned int n, unsigned int m, std::mt19937& rng) { while (true) { - unsigned int x = std::uniform_int_distribution(0U, n - 2)(rng); - unsigned int y = std::uniform_int_distribution(x + 1, n - 1)(rng); + std::uniform_int_distribution xdist{0, n - 2}; + unsigned int x = xdist(rng); + std::uniform_int_distribution ydist{x + 1, n - 1}; + unsigned int y = ydist(rng); bool ok = true; for (const auto& e : edges_info) { @@ -260,7 +262,8 @@ void run_random_graph(unsigned int n, unsigned int m, std::mt19937& rng) } if (ok) { - long w = std::uniform_int_distribution(0, 1000)(rng); + std::uniform_int_distribution wdist{0, 1000}; + long w = wdist(rng); edges_info.push_back(edge_info{x, y, w}); break; } From 69f3f342b28b7ff3d43a07810c8e8c98b96e93f5 Mon Sep 17 00:00:00 2001 From: Joris van Rantwijk Date: Thu, 5 Dec 2024 20:39:15 +0100 Subject: [PATCH 4/4] Fix incorrect use of abs() function --- test/weighted_matching_test2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/weighted_matching_test2.cpp b/test/weighted_matching_test2.cpp index 09fc3d365..36f955ef6 100644 --- a/test/weighted_matching_test2.cpp +++ b/test/weighted_matching_test2.cpp @@ -181,7 +181,7 @@ void test_matching(const Graph& g, WeightType answer) { WeightType eps = std::numeric_limits::epsilon(); WeightType max_error = std::max(eps, answer * eps); - same_answer = (abs(weight - answer) <= max_error); + same_answer = (std::abs(weight - answer) <= max_error); } BOOST_TEST(same_answer);