/* * Unit tests for maximum weight matching. * * Depends on the Boost.Test unit test framework. * Tested with Boost v1.74, available from https://www.boost.org/ */ #include #include #include #include #define BOOST_TEST_MODULE mwmatching #include #include "mwmatching.hpp" using EdgeVectorLong = std::vector>; 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) { EdgeVectorLong edges = {}; Matching expect = {}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test11_single_edge) { EdgeVectorLong edges = {{0, 1, 1}}; Matching expect = {{0, 1}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test12_simple) { EdgeVectorLong edges = {{1, 2, 10}, {2, 3, 11}}; Matching expect = {{2, 3}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test13_simple) { EdgeVectorLong 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(test15_float) { EdgeVectorDouble edges = {{1, 2, 3.1415}, {2, 3, 2.7183}, {1, 3, 3.0}, {1, 4, 1.4142}}; Matching expect = {{2, 3}, {1, 4}}; BOOST_TEST(mwmatching::maximum_weight_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, 2}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test20_sblossom) { EdgeVectorLong edges = {{1, 2, 8}, {1, 3, 9}, {2, 3, 10}, {3, 4, 7}}; Matching expect = {{1, 2}, {3, 4}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test20a_sblossom) { EdgeVectorLong 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(test21_tblossom) { EdgeVectorLong edges = {{1, 2, 9}, {1, 3, 8}, {2, 3, 10}, {1, 4, 5}, {4, 5, 4}, {1, 6, 3}}; Matching expect = {{2, 3}, {4, 5}, {1, 6}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test21a_tblossom) { EdgeVectorLong edges = {{1, 2, 9}, {1, 3, 8}, {2, 3, 10}, {1, 4, 5}, {4, 5, 3}, {1, 6, 4}}; Matching expect = {{2, 3}, {4, 5}, {1, 6}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test21b_tblossom) { EdgeVectorLong edges = {{1, 2, 9}, {1, 3, 8}, {2, 3, 10}, {1, 4, 5}, {4, 5, 3}, {3, 6, 4}}; Matching expect = {{1, 2}, {4, 5}, {3, 6}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test22_s_nest) { EdgeVectorLong edges = {{1, 2, 9}, {1, 3, 9}, {2, 3, 10}, {2, 4, 8}, {3, 5, 8}, {4, 5, 10}, {5, 6, 6}}; Matching expect = {{1, 3}, {2, 4}, {5, 6}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test23_s_relabel_nest) { EdgeVectorLong edges = { {1, 2, 10}, {1, 7, 10}, {2, 3, 12}, {3, 4, 20}, {3, 5, 20}, {4, 5, 25}, {5, 6, 10}, {6, 7, 10}, {7, 8, 8}}; Matching expect = {{1, 2}, {3, 4}, {5, 6}, {7, 8}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test24_s_nest_expand) { EdgeVectorLong edges = { {1, 2, 8}, {1, 3, 8}, {2, 3, 10}, {2, 4, 12}, {3, 5, 12}, {4, 5, 14}, {4, 6, 12}, {5, 7, 12}, {6, 7, 14}, {7, 8, 12}}; Matching expect = {{1, 2}, {3, 5}, {4, 6}, {7, 8}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test25_s_t_expand) { EdgeVectorLong edges = { {1, 2, 23}, {1, 5, 22}, {1, 6, 15}, {2, 3, 25}, {3, 4, 22}, {4, 5, 25}, {4, 8, 14}, {5, 7, 13}}; Matching expect = {{1, 6}, {2, 3}, {4, 8}, {5, 7}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test26_s_nest_t_expand) { EdgeVectorLong edges = { {1, 2, 19}, {1, 3, 20}, {1, 8, 8}, {2, 3, 25}, {2, 4, 18}, {3, 5, 18}, {4, 5, 13}, {4, 7, 7}, {5, 6, 7}}; Matching expect = {{1, 8}, {2, 3}, {4, 7}, {5, 6}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test30_tnasty_expand) { EdgeVectorLong edges = { {1, 2, 45}, {1, 5, 45}, {2, 3, 50}, {3, 4, 45}, {4, 5, 50}, {1, 6, 30}, {3, 9, 35}, {4, 8, 35}, {5, 7, 26}, {9, 10, 5}}; Matching expect = {{2, 3}, {1, 6}, {4, 8}, {5, 7}, {9, 10}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test31_tnasty_expand) { EdgeVectorLong edges = { {1, 2, 45}, {1, 5, 45}, {2, 3, 50}, {3, 4, 45}, {4, 5, 50}, {1, 6, 30}, {3, 9, 35}, {4, 8, 26}, {5, 7, 40}, {9, 10, 5}}; Matching expect = {{2, 3}, {1, 6}, {4, 8}, {5, 7}, {9, 10}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test32_t_expand_leastslack) { EdgeVectorLong edges = { {1, 2, 45}, {1, 5, 45}, {2, 3, 50}, {3, 4, 45}, {4, 5, 50}, {1, 6, 30}, {3, 9, 35}, {4, 8, 28}, {5, 7, 26}, {9, 10, 5}}; Matching expect = {{2, 3}, {1, 6}, {4, 8}, {5, 7}, {9, 10}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test33_nest_tnasty_expand) { EdgeVectorLong edges = { {1, 2, 45}, {1, 7, 45}, {2, 3, 50}, {3, 4, 45}, {4, 5, 95}, {4, 6, 94}, {5, 6, 94}, {6, 7, 50}, {1, 8, 30}, {3, 11, 35}, {5, 9, 36}, {7, 10, 26}, {11, 12, 5}}; Matching expect = {{2, 3}, {4, 6}, {1, 8}, {5, 9}, {7, 10}, {11, 12}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test34_nest_relabel_expand) { EdgeVectorLong edges = { {1, 2, 40}, {1, 3, 40}, {2, 3, 60}, {2, 4, 55}, {3, 5, 55}, {4, 5, 50}, {1, 8, 15}, {5, 7, 30}, {7, 6, 10}, {8, 10, 10}, {4, 9, 30}}; Matching expect = {{1, 2}, {3, 5}, {7, 6}, {8, 10}, {4, 9}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test41_nonmax_card) { 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 expect = {{1, 2}, {3, 4}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test42_s_nest_partial_expand) { /* * [0]--8--[1]--6--[3]--5--[5] * \ | | * \ 9 8 * 8 | | * \--[2]--7--[4] */ EdgeVectorLong edges = {{0, 1, 8}, {0, 2, 8}, {1, 2, 9}, {1, 3, 6}, {2, 4, 7}, {3, 4, 8}, {3, 5, 5}}; Matching expect = {{0, 1}, {2, 4}, {3, 5}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test43_s_nest_noexpand) { /* * [1]--9--[2] * | / * 7 ___7 * | / * [0] [5]--2--[6] * | \___ * 7 7 * | \ * [3]--9--[4] */ EdgeVectorLong edges = {{0, 1, 7}, {0, 2, 7}, {1, 2, 9}, {0, 3, 7}, {0, 4, 7}, {3, 4, 9}, {5, 6, 2}}; Matching expect = {{1, 2}, {3, 4}, {5, 6}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test44_blossom_redundant_edge) { /* * [1]----9---[2] * / | \ * 7 8 \ * / | 1 * [0]--6--[4]--9--[3] | * \ | * \----1----[5] */ EdgeVectorLong edges = {{0, 1, 7}, {0, 4, 6}, {1, 2, 9}, {2, 3, 8}, {3, 4, 9}, {3, 5, 1}, {4, 5, 1}}; Matching expect = {{1, 2}, {3, 4}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test45_flip_order) { EdgeVectorLong edges = { {0, 1, 2}, {0, 4, 3}, {1, 2, 7}, {1, 5, 1}, {2, 3, 9}, {2, 5, 4}, {3, 4, 8}, {3, 5, 4}}; Matching expect = {{1, 2}, {3, 4}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); EdgeVectorLong redges(edges.rbegin(), edges.rend()); Matching rexpect(expect.rbegin(), expect.rend()); BOOST_TEST(mwmatching::maximum_weight_matching(redges) == rexpect); for (mwmatching::Edge& edge : edges) { std::swap(edge.vt.first, edge.vt.second); } for (mwmatching::VertexPair& vt : expect) { std::swap(vt.first, vt.second); } BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); edges = {{0, 2, 4}, {0, 3, 4}, {0, 4, 1}, {1, 2, 8}, {1, 5, 3}, {2, 3, 9}, {3, 4, 7}, {4, 5, 2}}; expect = {{1, 2}, {3, 4}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test46_expand_unlabeled_blossom) { /* * 3--[1] * / | * [2]--1--[4] 7 * \ | * 5--[3]--5--[5] */ EdgeVectorLong edges = {{1, 3, 7}, {1, 4, 3}, {2, 4, 1}, {3, 4, 5}, {3, 5, 5}}; Matching expect1 = {{1, 3}, {2, 4}}; Matching expect2 = {{1, 4}, {3, 5}}; Matching result = mwmatching::maximum_weight_matching(edges); BOOST_TEST((result == expect1 || result == expect2)); } BOOST_AUTO_TEST_CASE(test47_expand_unlabeled_outer) { /* * [3]--10--[1]--15--[2]--12--[5] * _/ \_ | | * 11 16_ 8 15 * / \ | | * [4] [6]---7--[7] */ EdgeVectorLong edges = { {1, 2, 15}, {1, 3, 10}, {1, 4, 11}, {1, 6, 17}, {2, 5, 12}, {2, 6, 8}, {5, 7, 15}, {6, 7, 7}}; Matching expect = {{1, 4}, {2, 6}, {5, 7}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test48_expand_unlabeled_nested) { /* * [5]--11--[1]--11 14--[3] * | \ / | * 12 [4] 14 * | / \ | * [6]--11--[2]--12 11--[7] */ EdgeVectorLong edges = { {1, 2, 12}, {1, 4, 11}, {1, 5, 11}, {2, 4, 12}, {2, 6, 11}, {3, 4, 14}, {3, 7, 14}, {4, 7, 11}}; Matching expect = {{1, 5}, {2, 4}, {3, 7}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test51_augment_blossom_nested) { /* * 19--[1]--17--[4]--11--[7] * / | | * [3]--15--[2] 19 18 * \ | | * 21--[5]--19--[6] */ EdgeVectorLong edges = { {1, 2, 19}, {1, 4, 17}, {1, 5, 19}, {2, 3, 15}, {2, 5, 21}, {4, 6, 18}, {4, 7, 11}, {5, 6, 19}}; Matching expect = {{1, 5}, {2, 3}, {4, 6}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test52_augment_blossom_nested2) { /* * * [4]--15 19--[2] * | \ / | * 16 [1] 21 * | / \ | * [5]--17 19--[3] * | * 10 * | * [6] */ EdgeVectorLong edges = { {1, 2, 19}, {1, 3, 19}, {1, 4, 15}, {1, 5, 17}, {2, 3, 21}, {4, 5, 16}, {5, 6, 10}}; Matching expect = {{1, 4}, {2, 3}, {5, 6}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test61_triangles_n9) { /* * t.f 9 nodes * * [1]------[4] [7] * | \ | \ | \ * | [3] | [6] | [9] * | / | / | / * [2] [5]------[8] */ EdgeVectorLong edges = { {1, 2, 1}, {1, 3, 1}, {2, 3, 1}, {4, 5, 1}, {4, 6, 1}, {5, 6, 1}, {7, 8, 1}, {7, 9, 1}, {8, 9, 1}, {1, 4, 1}, {5, 8, 1}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges).size() == 4); } BOOST_AUTO_TEST_CASE(test_fail_bad_input) { EdgeVectorDouble inf_weight = {{1, 2, std::numeric_limits::infinity()}}; BOOST_CHECK_THROW(mwmatching::maximum_weight_matching(inf_weight), std::invalid_argument); EdgeVectorDouble huge_weight = {{1, 2, std::numeric_limits::max() / 2}}; BOOST_CHECK_THROW(mwmatching::maximum_weight_matching(huge_weight), std::invalid_argument); } BOOST_AUTO_TEST_CASE(test_fail_bad_graph) { EdgeVectorLong self_edge = {{0, 1, 2}, {1, 1, 1}}; BOOST_CHECK_THROW(mwmatching::maximum_weight_matching(self_edge), std::invalid_argument); EdgeVectorLong duplicate_edge = {{0, 1, 2}, {1, 2, 1}, {2, 1, 1}}; BOOST_CHECK_THROW(mwmatching::maximum_weight_matching(duplicate_edge), std::invalid_argument); } BOOST_AUTO_TEST_SUITE_END() /* ********** Test corner cases of maximum_weight_matching() ********** */ BOOST_AUTO_TEST_SUITE(test_corner_cases) BOOST_AUTO_TEST_CASE(test1) { EdgeVectorLong edges = {{0, 4, 26}, {1, 3, 31}, {1, 4, 49}}; Matching expect = {{0, 4}, {1, 3}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test2) { EdgeVectorLong edges = {{0, 2, 42}, {0, 4, 36}, {2, 3, 26}}; Matching expect = {{0, 4}, {2, 3}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test3) { EdgeVectorLong edges = {{0, 4, 43}, {1, 4, 28}, {2, 4, 38}}; Matching expect = {{0, 4}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test4) { EdgeVectorLong edges = {{0, 1, 50}, {0, 3, 46}, {0, 4, 45}}; Matching expect = {{0, 1}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test5) { EdgeVectorLong edges = {{0, 1, 35}, {0, 3, 36}, {0, 4, 46}}; Matching expect = {{0, 4}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test6) { EdgeVectorLong edges = {{0, 1, 50}, {0, 4, 51}, {0, 5, 34}, {1, 2, 43}, {1, 4, 57}, {2, 5, 47}, {3, 4, 17}}; Matching expect = {{0, 1}, {2, 5}, {3, 4}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test7) { EdgeVectorLong edges = {{0, 1, 34}, {0, 3, 19}, {1, 2, 45}, {1, 3, 30}, {1, 4, 37}, {2, 4, 36}}; Matching expect = {{0, 1}, {2, 4}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test8) { EdgeVectorLong edges = {{0, 1, 48}, {0, 3, 42}, {0, 4, 57}, {1, 3, 51}, {1, 5, 36}, {2, 3, 23}, {4, 5, 46}}; Matching expect = {{0, 1}, {2, 3}, {4, 5}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test9) { EdgeVectorLong edges = {{0, 1, 21}, {0, 2, 25}, {1, 4, 40}, {2, 3, 10}, {2, 5, 40}, {3, 5, 31}, {4, 5, 58}}; Matching expect = {{0, 2}, {1, 4}, {3, 5}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test10) { EdgeVectorLong edges = {{0, 2, 7}, {0, 5, 20}, {1, 2, 50}, {1, 4, 46}, {2, 3, 35}, {2, 4, 8}, {2, 5, 25}, {3, 5, 47}}; Matching expect = {{0, 5}, {1, 4}, {2, 3}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test11) { EdgeVectorLong edges = {{0, 1, 42}, {0, 2, 60}, {1, 3, 34}, {1, 4, 58}, {1, 5, 52}, {2, 5, 60}, {3, 5, 34}, {4, 5, 57}}; Matching expect = {{0, 2}, {1, 4}, {3, 5}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test12) { EdgeVectorLong edges = {{0, 1, 23}, {0, 2, 26}, {0, 3, 2}, {0, 4, 41}, {2, 4, 36}}; Matching expect = {{0, 1}, {2, 4}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test13) { EdgeVectorLong edges = {{0, 3, 58}, {0, 4, 49}, {1, 5, 34}, {2, 3, 22}, {2, 5, 42}, {4, 5, 36}}; Matching expect = {{0, 4}, {1, 5}, {2, 3}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test14) { EdgeVectorLong edges = {{0, 1, 29}, {0, 3, 35}, {0, 4, 42}, {1, 2, 12}, {2, 4, 29}, {3, 4, 44}}; Matching expect = {{0, 1}, {3, 4}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test15) { EdgeVectorLong edges = {{0, 4, 53}, {0, 5, 42}, {1, 4, 45}, {2, 4, 59}, {2, 6, 39}, {4, 5, 69}, {4, 6, 52}}; Matching expect = {{0, 5}, {1, 4}, {2, 6}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test16) { EdgeVectorLong edges = {{0, 2, 13}, {1, 2, 11}, {2, 3, 39}, {2, 4, 17}, {3, 4, 35}}; Matching expect = {{0, 2}, {3, 4}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test17) { EdgeVectorLong edges = {{0, 1, 48}, {0, 2, 44}, {0, 4, 48}, {1, 4, 36}, {3, 4, 31}}; Matching expect = {{0, 2}, {1, 4}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_SUITE_END() /* ********** 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() /* ********** Test verification ********** */ BOOST_AUTO_TEST_SUITE(test_verification) BOOST_AUTO_TEST_CASE(test_success) { using mwmatching::impl::NO_VERTEX; EdgeVectorLong edges = {{0, 1, 10}, {1, 2, 11}}; mwmatching::impl::MatchingContext ctx(edges); ctx.vertex_mate = {NO_VERTEX, 2, 1}; ctx.vertex_dual = {0, 20, 2}; ctx.nontrivial_blossom.clear(); mwmatching::impl::MatchingVerifier verifier(ctx); BOOST_TEST(verifier.verify() == true); } BOOST_AUTO_TEST_CASE(test_asymmetric_matching) { using mwmatching::impl::NO_VERTEX; EdgeVectorLong edges = {{0, 1, 10}, {1, 2, 11}}; mwmatching::impl::MatchingContext ctx(edges); ctx.vertex_mate = {NO_VERTEX, 2, 0}; ctx.vertex_dual = {0, 20, 2}; ctx.nontrivial_blossom.clear(); mwmatching::impl::MatchingVerifier verifier(ctx); BOOST_TEST(verifier.verify() == false); } BOOST_AUTO_TEST_CASE(test_nonexistent_matched_edge) { using mwmatching::impl::NO_VERTEX; EdgeVectorLong edges = {{0, 1, 10}, {1, 2, 11}}; mwmatching::impl::MatchingContext ctx(edges); ctx.vertex_mate = {2, NO_VERTEX, 0}; ctx.vertex_dual = {11, 11, 11}; ctx.nontrivial_blossom.clear(); mwmatching::impl::MatchingVerifier verifier(ctx); BOOST_TEST(verifier.verify() == false); } BOOST_AUTO_TEST_CASE(test_negative_vertex_dual) { using mwmatching::impl::NO_VERTEX; EdgeVectorLong edges = {{0, 1, 10}, {1, 2, 11}}; mwmatching::impl::MatchingContext ctx(edges); ctx.vertex_mate = {NO_VERTEX, 2, 1}; ctx.vertex_dual = {-2, 22, 0}; ctx.nontrivial_blossom.clear(); mwmatching::impl::MatchingVerifier verifier(ctx); BOOST_TEST(verifier.verify() == false); } BOOST_AUTO_TEST_CASE(test_unmatched_nonzero_dual) { using mwmatching::impl::NO_VERTEX; EdgeVectorLong edges = {{0, 1, 10}, {1, 2, 11}}; mwmatching::impl::MatchingContext ctx(edges); ctx.vertex_mate = {NO_VERTEX, 2, 1}; ctx.vertex_dual = {9, 11, 11}; ctx.nontrivial_blossom.clear(); mwmatching::impl::MatchingVerifier verifier(ctx); BOOST_TEST(verifier.verify() == false); } BOOST_AUTO_TEST_CASE(test_negative_edge_slack) { using mwmatching::impl::NO_VERTEX; EdgeVectorLong edges = {{0, 1, 10}, {1, 2, 11}}; mwmatching::impl::MatchingContext ctx(edges); ctx.vertex_mate = {NO_VERTEX, 2, 1}; ctx.vertex_dual = {0, 11, 11}; ctx.nontrivial_blossom.clear(); mwmatching::impl::MatchingVerifier verifier(ctx); BOOST_TEST(verifier.verify() == false); } BOOST_AUTO_TEST_CASE(test_matched_edge_slack) { using mwmatching::impl::NO_VERTEX; EdgeVectorLong edges = {{0, 1, 10}, {1, 2, 11}}; mwmatching::impl::MatchingContext ctx(edges); ctx.vertex_mate = {NO_VERTEX, 2, 1}; ctx.vertex_dual = {0, 20, 11}; ctx.nontrivial_blossom.clear(); mwmatching::impl::MatchingVerifier verifier(ctx); BOOST_TEST(verifier.verify() == false); } BOOST_AUTO_TEST_CASE(test_negative_blossom_dual) { EdgeVectorLong edges = {{0, 1, 7}, {0, 2, 8}, {1, 2, 9}, {2, 3, 6}}; mwmatching::impl::MatchingContext ctx(edges); ctx.vertex_mate = {1, 0, 3, 2}; ctx.vertex_dual = {4, 6, 8, 4}; mwmatching::impl::Blossom b0(0); mwmatching::impl::Blossom b1(1); mwmatching::impl::Blossom b2(2); ctx.nontrivial_blossom.emplace_back( std::vector*>{&b0, &b1, &b2}, std::deque{{0, 1}, {1, 2}, {2, 0}}); ctx.nontrivial_blossom.front().dual_var = -2; mwmatching::impl::MatchingVerifier verifier(ctx); BOOST_TEST(verifier.verify() == false); } BOOST_AUTO_TEST_CASE(test_blossom_not_full) { using mwmatching::impl::NO_VERTEX; EdgeVectorLong edges = {{0, 1, 7}, {0, 2, 2}, {1, 2, 5}, {0, 3, 8}, {1, 4, 8}}; mwmatching::impl::MatchingContext ctx(edges); ctx.vertex_mate = {3, 4, NO_VERTEX, 0, 1}; ctx.vertex_dual = {4, 10, 0, 12, 6}; mwmatching::impl::Blossom b0(0); mwmatching::impl::Blossom b1(1); mwmatching::impl::Blossom b2(2); ctx.nontrivial_blossom.emplace_back( std::vector*>{&b0, &b1, &b2}, std::deque{{0, 1}, {1, 2}, {2, 0}}); ctx.nontrivial_blossom.front().dual_var = 2; mwmatching::impl::MatchingVerifier verifier(ctx); BOOST_TEST(verifier.verify() == false); } BOOST_AUTO_TEST_SUITE_END() /* ********** Test graphs that force big values for dual / slack ********** */ BOOST_AUTO_TEST_SUITE(test_value_range) BOOST_AUTO_TEST_CASE(test_big_blossom_dual) { /* * Force modified blossom dual close to 2*maxweight. * * [3] * / \ * W-4/ \W * 7 / \ * [1]-----[2]-----[4] * | W-4 * 5| * | 1 * [5]-----[6] */ long w = std::numeric_limits::max() / 6; EdgeVectorLong edges = {{1, 2, 7}, {2, 3, w - 4}, {2, 4, w - 4}, {2, 5, 5}, {3, 4, w}, {5, 6, 1}}; Matching expect = {{1, 2}, {3, 4}, {5, 6}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test_negative_blossom_dual) { /* * Force modified blossom dual close to -maxweight. * * [3] * / \ * 5/ \7 * 1 / \ * [1]-----[2]-----[4] * | 5 * 1| * | W * [5]-----[6] */ long w = std::numeric_limits::max() / 6; EdgeVectorLong edges = {{1, 2, 1}, {2, 3, 5}, {2, 4, 5}, {2, 5, 1}, {3, 4, 7}, {5, 6, w}}; Matching expect = {{1, 2}, {3, 4}, {5, 6}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_CASE(test_big_edge_slack) { /* * Force modified edge slack close to 3*maxweight. * * 6 W W-2 5 * [1]-----[2]-----[3]-----[4]-----[5] * | | * |1 |1 * | | * [6]-----[7]-----[8]-----[9]-----[10] * 6 W W-2 5 */ long w = std::numeric_limits::max() / 6; EdgeVectorLong edges = {{1, 2, 6}, {1, 6, 1}, {2, 3, w}, {3, 4, w - 2}, {3, 8, 1}, {4, 5, 5}, {6, 7, 6}, {7, 8, w}, {8, 9, w - 2}, {9, 10, 5}}; Matching expect = {{1, 6}, {2, 3}, {4, 5}, {7, 8}, {9, 10}}; BOOST_TEST(mwmatching::maximum_weight_matching(edges) == expect); } BOOST_AUTO_TEST_SUITE_END()