From 8d7d1a537ab94781dbc95a59edb6eab29cc3b5e4 Mon Sep 17 00:00:00 2001 From: Joris van Rantwijk Date: Fri, 12 May 2023 18:11:02 +0200 Subject: [PATCH] Add C++ tests for maximum cardinality matching --- cpp/test_mwmatching.cpp | 217 ++++++++++++++++++++++++++++------------ 1 file changed, 151 insertions(+), 66 deletions(-) diff --git a/cpp/test_mwmatching.cpp b/cpp/test_mwmatching.cpp index 80870df..b34d986 100644 --- a/cpp/test_mwmatching.cpp +++ b/cpp/test_mwmatching.cpp @@ -21,6 +21,8 @@ using EdgeVectorDouble = std::vector>; using Matching = std::vector; +/* ********** Test maximum_weight_matching() ********** */ + BOOST_AUTO_TEST_SUITE(test_maximum_weight_matching) BOOST_AUTO_TEST_CASE(test10_empty) @@ -325,6 +327,21 @@ BOOST_AUTO_TEST_CASE(test51_augment_blossom_nested) BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } +BOOST_AUTO_TEST_CASE(test61_unsigned) +{ + std::vector> edges = {{1, 2, 5}, {2, 3, 11}, {3, 4, 5}}; + Matching expect = {{2, 3}}; + BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); +} + +BOOST_AUTO_TEST_CASE(test62_unsigned2) +{ + std::vector> edges = { + {1, 2, 8}, {1, 3, 9}, {2, 3, 10}, {3, 4, 7}, {1, 6, 5}, {4, 5, 6}}; + Matching expect = {{2, 3}, {1, 6}, {4, 5}}; + BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); +} + BOOST_AUTO_TEST_CASE(test_fail_bad_input) { EdgeVectorDouble inf_weight = {{1, 2, std::numeric_limits::infinity()}}; @@ -343,13 +360,146 @@ BOOST_AUTO_TEST_CASE(test_fail_bad_graph) BOOST_CHECK_THROW(mwmatching::maximum_weight_matching(duplicate_edge), std::invalid_argument); } +BOOST_AUTO_TEST_SUITE_END() -// TODO : test with unsigned weight type +/* ********** Test adjust_weights_for_maximum_cardinality_matching() ********** */ + +template +static bool equal_weight(WeightType lhs, WeightType rhs); + +template <> +bool equal_weight(long lhs, long rhs) +{ + return (lhs == rhs); +} + +template <> +bool equal_weight(double lhs, double rhs) +{ + static const boost::math::fpc::small_with_tolerance eq(1e-6); + return eq(lhs - rhs); +} + +template +static bool equal_edges(const std::vector>& lhs, + const std::vector>& rhs) +{ + if (lhs.size() != rhs.size()) { + return false; + } + auto li = lhs.begin(); + auto ri = rhs.begin(); + while ((li != lhs.end()) && (ri != rhs.end())) { + if (li->vt != ri->vt) { + return false; + } + if (! equal_weight(li->weight, ri->weight)) { + return false; + } + ++li; + ++ri; + } + return (li == lhs.end()) && (ri == rhs.end()); +} + +BOOST_AUTO_TEST_SUITE(test_adjust_weights_for_maximum_cardinality) + +BOOST_AUTO_TEST_CASE(test_empty) +{ + EdgeVectorLong edges = {}; + EdgeVectorLong expect = {}; + BOOST_TEST(equal_edges(mwmatching::adjust_weights_for_maximum_cardinality_matching(edges), expect)); +} + +BOOST_AUTO_TEST_CASE(test_chain) +{ + EdgeVectorLong edges = {{0, 1, 2}, {1, 2, 8}, {2, 3, 3}, {3, 4, 9}, {4, 5, 1}, {5, 6, 7}, {6, 7, 4}}; + EdgeVectorLong expect = {{0, 1, 65}, {1, 2, 71}, {2, 3, 66}, {3, 4, 72}, {4, 5, 64}, {5, 6, 70}, {6, 7, 67}}; + BOOST_TEST(equal_edges(mwmatching::adjust_weights_for_maximum_cardinality_matching(edges), expect)); +} + +BOOST_AUTO_TEST_CASE(test_chain_preadjusted) +{ + EdgeVectorLong edges = {{0, 1, 65}, {1, 2, 71}, {2, 3, 66}, {3, 4, 72}, {4, 5, 64}, {5, 6, 70}, {6, 7, 67}}; + BOOST_TEST(equal_edges(mwmatching::adjust_weights_for_maximum_cardinality_matching(edges), edges)); +} + +BOOST_AUTO_TEST_CASE(test_flat) +{ + EdgeVectorLong edges = {{0, 1, 0}, {0, 4, 0}, {1, 2, 0}, {1, 5, 0}, {2, 3, 0}, {2, 5, 0}, {3, 4, 0}, {3, 5, 0}}; + EdgeVectorLong expect = {{0, 1, 1}, {0, 4, 1}, {1, 2, 1}, {1, 5, 1}, {2, 3, 1}, {2, 5, 1}, {3, 4, 1}, {3, 5, 1}}; + BOOST_TEST(equal_edges(mwmatching::adjust_weights_for_maximum_cardinality_matching(edges), expect)); +} + +BOOST_AUTO_TEST_CASE(test_simple) +{ + EdgeVectorLong edges = {{1, 2, 5}, {2, 3, 11}, {3, 4, 5}}; + EdgeVectorLong expect = {{1, 2, 30}, {2, 3, 36}, {3, 4, 30}}; + BOOST_TEST(equal_edges(mwmatching::adjust_weights_for_maximum_cardinality_matching(edges), expect)); +} + +BOOST_AUTO_TEST_CASE(test_negative) +{ + EdgeVectorLong edges = {{1, 2, 2}, {1, 3, -2}, {2, 3, 1}, {2, 4, -1}, {3, 4, -6}}; + EdgeVectorLong expect = {{1, 2, 48}, {1, 3, 44}, {2, 3, 47}, {2, 4, 45}, {3, 4, 40}}; + BOOST_TEST(equal_edges(mwmatching::adjust_weights_for_maximum_cardinality_matching(edges), expect)); +} + +BOOST_AUTO_TEST_CASE(test_float) +{ + EdgeVectorDouble edges = {{0, 1, 1}, {1, 2, 1.4142}, {2, 3, 1.6180}, {3, 4, 2.7182}, {4, 5, 3.1415}}; + EdgeVectorDouble expect = {{0, 1, 12.8490}, {1, 2, 13.2632}, {2, 3, 13.4670}, {3, 4, 14.5672}, {4, 5, 14.9905}}; + BOOST_TEST(equal_edges(mwmatching::adjust_weights_for_maximum_cardinality_matching(edges), expect)); +} + +BOOST_AUTO_TEST_CASE(test_fail_bad_range) +{ + const long max_long = std::numeric_limits::max(); + EdgeVectorLong edges = {{0, 1, -(max_long / 8)}, {1, 2, 0}, {2, 3, max_long / 8}}; + BOOST_CHECK_THROW(mwmatching::adjust_weights_for_maximum_cardinality_matching(edges), std::invalid_argument); +} BOOST_AUTO_TEST_SUITE_END() +/* ********** Test maximum cardinality matching ********** */ + +BOOST_AUTO_TEST_SUITE(test_maximum_cardinality_matching) + +BOOST_AUTO_TEST_CASE(test14_maxcard) +{ + EdgeVectorLong edges = {{1, 2, 5}, {2, 3, 11}, {3, 4, 5}}; + Matching expect = {{1, 2}, {3, 4}}; + BOOST_TEST( + mwmatching::maximum_weight_matching( + mwmatching::adjust_weights_for_maximum_cardinality_matching(edges)) == expect); +} + +BOOST_AUTO_TEST_CASE(test16_negative) +{ + EdgeVectorLong edges = {{1, 2, 2}, {1, 3, -2}, {2, 3, 1}, {2, 4, -1}, {3, 4, -6}}; + Matching expect = {{1, 3}, {2, 4}}; + BOOST_TEST( + mwmatching::maximum_weight_matching( + mwmatching::adjust_weights_for_maximum_cardinality_matching(edges)) == expect); +} + +BOOST_AUTO_TEST_CASE(test41_maxcard) +{ + EdgeVectorLong edges = {{0, 1, 2}, {0, 4, 3}, {1, 2, 7}, {1, 5, 2}, {2, 3, 9}, {2, 5, 4}, {3, 4, 8}, {3, 5, 4}}; + Matching expect1 = {{0, 1}, {2, 5}, {3, 4}}; + Matching expect2 = {{0, 4}, {1, 2}, {3, 5}}; + Matching result = + mwmatching::maximum_weight_matching( + mwmatching::adjust_weights_for_maximum_cardinality_matching(edges)); + BOOST_TEST((result == expect1 || result == expect2)); +} + +BOOST_AUTO_TEST_SUITE_END() + + + /* class TestCornerCases(unittest.TestCase): @@ -428,71 +578,6 @@ class TestCornerCases(unittest.TestCase): self.assertEqual(pairs, [(0,2), (1,4)]) -class TestAdjustWeightForMaxCardinality(unittest.TestCase): - """Test adjust_weights_for_maximum_cardinality_matching() function.""" - - def test_empty(self): - self.assertEqual(adj([]), []) - - def test_chain(self): - self.assertEqual( - adj([(0,1,2), (1,2,8), (2,3,3), (3,4,9), (4,5,1), (5,6,7), (6,7,4)]), - [(0,1,65), (1,2,71), (2,3,66), (3,4,72), (4,5,64), (5,6,70), (6,7,67)]) - - def test_chain_preadjusted(self): - self.assertEqual( - adj([(0,1,65), (1,2,71), (2,3,66), (3,4,72), (4,5,64), (5,6,70), (6,7,67)]), - [(0,1,65), (1,2,71), (2,3,66), (3,4,72), (4,5,64), (5,6,70), (6,7,67)]) - - def test_flat(self): - self.assertEqual( - adj([(0,1,0), (0,4,0), (1,2,0), (1,5,0), (2,3,0), (2,5,0), (3,4,0), (3,5,0)]), - [(0,1,1), (0,4,1), (1,2,1), (1,5,1), (2,3,1), (2,5,1), (3,4,1), (3,5,1)]) - - def test14_maxcard(self): - self.assertEqual( - adj([(1,2,5), (2,3,11), (3,4,5)]), - [(1,2,30), (2,3,36), (3,4,30)]) - - def test16_negative(self): - self.assertEqual( - adj([(1,2,2), (1,3,-2), (2,3,1), (2,4,-1), (3,4,-6)]), - [(1,2,48), (1,3,44), (2,3,47), (2,4,45), (3,4,40)]) - - -class TestMaximumCardinalityMatching(unittest.TestCase): - """Test maximum cardinality matching.""" - - def test14_maxcard(self): - """maximum cardinality""" - self.assertEqual( - mwm(adj([(1,2,5), (2,3,11), (3,4,5)])), - [(1,2), (3,4)]) - - def test16_negative(self): - """negative weights""" - self.assertEqual( - mwm(adj([(1,2,2), (1,3,-2), (2,3,1), (2,4,-1), (3,4,-6)])), - [(1,3), (2,4)]) - - def test43_maxcard(self): - """maximum cardinality""" - self.assertIn( - mwm(adj([(0,1,2), (0,4,3), (1,2,7), (1,5,2), (2,3,9), (2,5,4), (3,4,8), (3,5,4)])), - ([(0,1), (2,5), (3,4)], - [(0,4), (1,2), (3,5)])) - - -class TestGraphInfo(unittest.TestCase): - """Test _GraphInfo helper class.""" - - def test_empty(self): - graph = mwmatching._GraphInfo([]) - self.assertEqual(graph.num_vertex, 0) - self.assertEqual(graph.edges, []) - self.assertEqual(graph.adjacent_edges, []) - - class TestVerificationFail(unittest.TestCase): """Test failure handling in verification routine."""