1
0
Fork 0

Implement algorithm in C++

This commit is contained in:
Joris van Rantwijk 2023-05-10 20:56:30 +02:00
parent 082a2d8f03
commit 61cb309082
3 changed files with 2791 additions and 0 deletions

12
cpp/Makefile Normal file
View File

@ -0,0 +1,12 @@
CXX = g++
CXXFLAGS = -std=c++11 -Wall -O2 -fsanitize=address -fsanitize=undefined
LDLIBS = -l:libboost_unit_test_framework.a
test_mwmatching: test_mwmatching.cpp mwmatching.h
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS)
.PHONY: clean
clean:
$(RM) test_mwmatching

2161
cpp/mwmatching.h Normal file

File diff suppressed because it is too large Load Diff

618
cpp/test_mwmatching.cpp Normal file
View File

@ -0,0 +1,618 @@
/*
* 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 <algorithm>
#include <limits>
#include <stdexcept>
#include <vector>
#define BOOST_TEST_MODULE mwmatching
#include <boost/test/unit_test.hpp>
#include "mwmatching.h"
using EdgeVectorLong = std::vector<mwmatching::Edge<long>>;
using EdgeVectorDouble = std::vector<mwmatching::Edge<double>>;
using Matching = std::vector<mwmatching::VertexPair>;
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<long>& 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(test_fail_bad_input)
{
EdgeVectorDouble inf_weight = {{1, 2, std::numeric_limits<double>::infinity()}};
BOOST_CHECK_THROW(mwmatching::maximum_weight_matching(inf_weight), std::invalid_argument);
EdgeVectorDouble huge_weight = {{1, 2, std::numeric_limits<double>::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);
}
// TODO : test with unsigned weight type
BOOST_AUTO_TEST_SUITE_END()
/*
class TestCornerCases(unittest.TestCase):
"""Test cases that would catch certain errors in the algorithm.
These graphs were generated semi-automatically to fail when
specific bugs are introduced in the code.
"""
def test1(self):
pairs = mwm([(0,4,26), (1,3,31), (1,4,49)])
self.assertEqual(pairs, [(0,4), (1,3)])
def test2(self):
pairs = mwm([(0,2,42), (0,4,36), (2,3,26)])
self.assertEqual(pairs, [(0,4), (2,3)])
def test3(self):
pairs = mwm([(0,4,43), (1,4,28), (2,4,38)])
self.assertEqual(pairs, [(0,4)])
def test4(self):
pairs = mwm([(0,1,50), (0,3,46), (0,4,45)])
self.assertEqual(pairs, [(0,1)])
def test5(self):
pairs = mwm([(0,1,35), (0,3,36), (0,4,46)])
self.assertEqual(pairs, [(0,4)])
def test6(self):
pairs = mwm([(0,1,50), (0,4,51), (0,5,34), (1,2,43), (1,4,57), (2,5,47), (3,4,17)])
self.assertEqual(pairs, [(0,1), (2,5), (3,4)])
def test7(self):
pairs = mwm([(0,1,34), (0,3,19), (1,2,45), (1,3,30), (1,4,37), (2,4,36)])
self.assertEqual(pairs, [(0,1), (2,4)])
def test8(self):
pairs = mwm([(0,1,48), (0,3,42), (0,4,57), (1,3,51), (1,5,36), (2,3,23), (4,5,46)])
self.assertEqual(pairs, [(0,1), (2,3), (4,5)])
def test9(self):
pairs = mwm([(0,1,21), (0,2,25), (0,5,42), (1,4,40), (2,3,10), (2,5,40), (3,5,31), (4,5,58)])
self.assertEqual(pairs, [(0,2), (1,4), (3,5)])
def test10(self):
pairs = mwm([(0,2,7), (0,5,20), (1,2,50), (1,4,46), (2,3,35), (2,4,8), (2,5,25), (3,5,47)])
self.assertEqual(pairs, [(0,5), (1,4), (2,3)])
def test11(self):
pairs = mwm([(0,1,42), (0,2,60), (1,3,34), (1,4,58), (1,5,52), (2,5,60), (3,5,34), (4,5,57)])
self.assertEqual(pairs, [(0,2), (1,4), (3,5)])
def test12(self):
pairs = mwm([(0,1,23), (0,2,26), (0,3,22), (0,4,41), (2,4,36)])
self.assertEqual(pairs, [(0,1), (2,4)])
def test13(self):
pairs = mwm([(0,3,58), (0,4,49), (1,5,34), (2,3,22), (2,5,42), (4,5,36)])
self.assertEqual(pairs, [(0,4), (1,5), (2,3)])
def test14(self):
pairs = mwm([(0,1,29), (0,3,35), (0,4,42), (1,2,12), (2,4,29), (3,4,44)])
self.assertEqual(pairs, [(0,1), (3,4)])
def test15(self):
pairs = mwm([(0,4,53), (0,5,42), (1,4,45), (2,4,59), (2,6,39), (4,5,69), (4,6,52)])
self.assertEqual(pairs, [(0,5), (1,4), (2,6)])
def test16(self):
pairs = mwm([(0,2,13), (1,2,11), (2,3,39), (2,4,17), (3,4,35)])
self.assertEqual(pairs, [(0,2), (3,4)])
def test17(self):
pairs = mwm([(0,1,48), (0,2,44), (0,4,48), (1,4,36), (3,4,31)])
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."""
def _make_context(
self,
edges,
vertex_mate,
vertex_dual_2x,
nontrivial_blossom):
ctx = Mock(spec=mwmatching._MatchingContext)
ctx.graph = mwmatching._GraphInfo(edges)
ctx.vertex_mate = vertex_mate
ctx.vertex_dual_2x = vertex_dual_2x
ctx.nontrivial_blossom = nontrivial_blossom
return ctx
def test_success(self):
edges = [(0,1,10), (1,2,11)]
ctx = self._make_context(
edges,
vertex_mate=[-1, 2, 1],
vertex_dual_2x=[0, 20, 2],
nontrivial_blossom=[])
mwmatching._verify_optimum(ctx)
def test_asymmetric_matching(self):
edges = [(0,1,10), (1,2,11)]
ctx = self._make_context(
edges,
vertex_mate=[-1, 2, 0],
vertex_dual_2x=[0, 20, 2],
nontrivial_blossom=[])
with self.assertRaises(mwmatching.MatchingError):
mwmatching._verify_optimum(ctx)
def test_nonexistent_matched_edge(self):
edges = [(0,1,10), (1,2,11)]
ctx = self._make_context(
edges,
vertex_mate=[2, -1, 0],
vertex_dual_2x=[11, 11, 11],
nontrivial_blossom=[])
with self.assertRaises(mwmatching.MatchingError):
mwmatching._verify_optimum(ctx)
def test_negative_vertex_dual(self):
edges = [(0,1,10), (1,2,11)]
ctx = self._make_context(
edges,
vertex_mate=[-1, 2, 1],
vertex_dual_2x=[-2, 22, 0],
nontrivial_blossom=[])
with self.assertRaises(mwmatching.MatchingError):
mwmatching._verify_optimum(ctx)
def test_unmatched_nonzero_dual(self):
edges = [(0,1,10), (1,2,11)]
ctx = self._make_context(
edges,
vertex_mate=[-1, 2, 1],
vertex_dual_2x=[9, 11, 11],
nontrivial_blossom=[])
with self.assertRaises(mwmatching.MatchingError):
mwmatching._verify_optimum(ctx)
def test_negative_edge_slack(self):
edges = [(0,1,10), (1,2,11)]
ctx = self._make_context(
edges,
vertex_mate=[-1, 2, 1],
vertex_dual_2x=[0, 11, 11],
nontrivial_blossom=[])
with self.assertRaises(mwmatching.MatchingError):
mwmatching._verify_optimum(ctx)
def test_matched_edge_slack(self):
edges = [(0,1,10), (1,2,11)]
ctx = self._make_context(
edges,
vertex_mate=[-1, 2, 1],
vertex_dual_2x=[0, 20, 11],
nontrivial_blossom=[])
with self.assertRaises(mwmatching.MatchingError):
mwmatching._verify_optimum(ctx)
def test_negative_blossom_dual(self):
#
# [0]--7--[1]--9--[2]--6--[3]
# \ /
# \----8-----/
#
edges = [(0,1,7), (0,2,8), (1,2,9), (2,3,6)]
blossom = mwmatching._NonTrivialBlossom(
subblossoms=[
mwmatching._Blossom(0),
mwmatching._Blossom(1),
mwmatching._Blossom(2)],
edges=[0,2,1])
for sub in blossom.subblossoms:
sub.parent = blossom
blossom.dual_var = -1
ctx = self._make_context(
edges,
vertex_mate=[1, 0, 3, 2],
vertex_dual_2x=[4, 6, 8, 4],
nontrivial_blossom=[blossom])
with self.assertRaises(mwmatching.MatchingError):
mwmatching._verify_optimum(ctx)
def test_blossom_not_full(self):
#
# [3] [4]
# | |
# 8 8
# | |
# [0]--7--[1]--5--[2]
# \ /
# \----2-----/
#
edges = [(0,1,7), (0,2,2), (1,2,5), (0,3,8), (1,4,8)]
blossom = mwmatching._NonTrivialBlossom(
subblossoms=[
mwmatching._Blossom(0),
mwmatching._Blossom(1),
mwmatching._Blossom(2)],
edges=[0,2,1])
for sub in blossom.subblossoms:
sub.parent = blossom
blossom.dual_var = 2
ctx = self._make_context(
edges,
vertex_mate=[3, 4, -1, 0, 1],
vertex_dual_2x=[4, 10, 0, 12, 6],
nontrivial_blossom=[blossom])
with self.assertRaises(mwmatching.MatchingError):
mwmatching._verify_optimum(ctx)
*/