graph_matching 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rubocop.yml +112 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +9 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +205 -0
  9. data/Rakefile +9 -0
  10. data/benchmark/mcm_bipartite/complete_bigraphs/benchmark.rb +33 -0
  11. data/benchmark/mcm_bipartite/complete_bigraphs/compare.gnuplot +19 -0
  12. data/benchmark/mcm_bipartite/complete_bigraphs/edges_times_vertexes.data +500 -0
  13. data/benchmark/mcm_bipartite/complete_bigraphs/plot.gnuplot +21 -0
  14. data/benchmark/mcm_bipartite/complete_bigraphs/plot.png +0 -0
  15. data/benchmark/mcm_bipartite/complete_bigraphs/time.data +499 -0
  16. data/benchmark/mcm_general/complete_graphs/benchmark.rb +30 -0
  17. data/benchmark/mcm_general/complete_graphs/plot.gnuplot +19 -0
  18. data/benchmark/mcm_general/complete_graphs/plot.png +0 -0
  19. data/benchmark/mcm_general/complete_graphs/time.data +499 -0
  20. data/benchmark/mcm_general/complete_graphs/v_cubed.data +500 -0
  21. data/benchmark/mwm_bipartite/complete_bigraphs/benchmark.rb +43 -0
  22. data/benchmark/mwm_bipartite/complete_bigraphs/nmN.data +499 -0
  23. data/benchmark/mwm_bipartite/complete_bigraphs/nmN.xlsx +0 -0
  24. data/benchmark/mwm_bipartite/complete_bigraphs/plot.gnuplot +22 -0
  25. data/benchmark/mwm_bipartite/complete_bigraphs/plot.png +0 -0
  26. data/benchmark/mwm_bipartite/complete_bigraphs/time.data +299 -0
  27. data/benchmark/mwm_bipartite/misc/calc_d2/benchmark.rb +29 -0
  28. data/benchmark/mwm_general/complete_graphs/benchmark.rb +32 -0
  29. data/benchmark/mwm_general/complete_graphs/compare.gnuplot +19 -0
  30. data/benchmark/mwm_general/complete_graphs/mn_log_n.data +299 -0
  31. data/benchmark/mwm_general/complete_graphs/mn_log_n.xlsx +0 -0
  32. data/benchmark/mwm_general/complete_graphs/plot.gnuplot +22 -0
  33. data/benchmark/mwm_general/complete_graphs/plot.png +0 -0
  34. data/benchmark/mwm_general/complete_graphs/time.data +299 -0
  35. data/benchmark/mwm_general/incomplete_graphs/benchmark.rb +39 -0
  36. data/benchmark/mwm_general/incomplete_graphs/plot.gnuplot +22 -0
  37. data/benchmark/mwm_general/incomplete_graphs/plot.png +0 -0
  38. data/benchmark/mwm_general/incomplete_graphs/time_10_pct.data +299 -0
  39. data/benchmark/mwm_general/incomplete_graphs/time_20_pct.data +299 -0
  40. data/benchmark/mwm_general/incomplete_graphs/time_30_pct.data +299 -0
  41. data/graph_matching.gemspec +35 -0
  42. data/lib/graph_matching.rb +15 -0
  43. data/lib/graph_matching/algorithm/matching_algorithm.rb +23 -0
  44. data/lib/graph_matching/algorithm/mcm_bipartite.rb +118 -0
  45. data/lib/graph_matching/algorithm/mcm_general.rb +289 -0
  46. data/lib/graph_matching/algorithm/mwm_bipartite.rb +147 -0
  47. data/lib/graph_matching/algorithm/mwm_general.rb +1086 -0
  48. data/lib/graph_matching/algorithm/mwmg_delta_assertions.rb +94 -0
  49. data/lib/graph_matching/assertion.rb +41 -0
  50. data/lib/graph_matching/core_ext/set.rb +36 -0
  51. data/lib/graph_matching/directed_edge_set.rb +31 -0
  52. data/lib/graph_matching/errors.rb +23 -0
  53. data/lib/graph_matching/graph/bigraph.rb +37 -0
  54. data/lib/graph_matching/graph/graph.rb +63 -0
  55. data/lib/graph_matching/graph/weighted.rb +112 -0
  56. data/lib/graph_matching/graph/weighted_bigraph.rb +17 -0
  57. data/lib/graph_matching/graph/weighted_graph.rb +17 -0
  58. data/lib/graph_matching/integer_vertexes.rb +29 -0
  59. data/lib/graph_matching/matching.rb +120 -0
  60. data/lib/graph_matching/ordered_set.rb +59 -0
  61. data/lib/graph_matching/version.rb +6 -0
  62. data/lib/graph_matching/visualize.rb +93 -0
  63. data/profile/mcm_bipartite/compare.sh +15 -0
  64. data/profile/mcm_bipartite/publish.sh +12 -0
  65. data/profile/mwm_general/compare.sh +15 -0
  66. data/profile/mwm_general/profile.rb +28 -0
  67. data/profile/mwm_general/publish.sh +12 -0
  68. data/research/1965_edmonds.pdf +0 -0
  69. data/research/1975_even_kariv.pdf +0 -0
  70. data/research/1976_gabow.pdf +0 -0
  71. data/research/1980_micali_vazirani.pdf +0 -0
  72. data/research/1985_gabow.pdf +0 -0
  73. data/research/2002_tarjan.pdf +0 -0
  74. data/research/2013_zwick.pdf +0 -0
  75. data/research/examples/unweighted_general/1.txt +86 -0
  76. data/research/goodwin.pdf +0 -0
  77. data/research/kavathekar-scribe.pdf +0 -0
  78. data/research/kusner.pdf +0 -0
  79. data/research/van_rantwijk/mwm_example.py +19 -0
  80. data/research/van_rantwijk/mwmatching.py +945 -0
  81. data/spec/graph_matching/algorithm/matching_algorithm_spec.rb +14 -0
  82. data/spec/graph_matching/algorithm/mcm_bipartite_spec.rb +98 -0
  83. data/spec/graph_matching/algorithm/mcm_general_spec.rb +159 -0
  84. data/spec/graph_matching/algorithm/mwm_bipartite_spec.rb +82 -0
  85. data/spec/graph_matching/algorithm/mwm_general_spec.rb +439 -0
  86. data/spec/graph_matching/graph/bigraph_spec.rb +73 -0
  87. data/spec/graph_matching/graph/graph_spec.rb +53 -0
  88. data/spec/graph_matching/graph/weighted_spec.rb +29 -0
  89. data/spec/graph_matching/integer_vertexes_spec.rb +21 -0
  90. data/spec/graph_matching/matching_spec.rb +89 -0
  91. data/spec/graph_matching/visualize_spec.rb +38 -0
  92. data/spec/graph_matching_spec.rb +9 -0
  93. data/spec/spec_helper.rb +26 -0
  94. metadata +263 -0
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe GraphMatching::Algorithm::MatchingAlgorithm do
6
+ let(:algorithm) { described_class.new(double) }
7
+
8
+ describe '#assert' do
9
+ it 'returns an Assertion' do
10
+ expect(algorithm.assert('banana')).to \
11
+ be_a(GraphMatching::Assertion)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe GraphMatching::Algorithm::MCMBipartite do
6
+ let(:g) { GraphMatching::Graph::Bigraph.new }
7
+
8
+ describe '.new' do
9
+ it 'requires a Bigraph' do
10
+ expect { described_class.new("banana") }.to raise_error(TypeError)
11
+ end
12
+ end
13
+
14
+ describe '#augment' do
15
+ it 'augments the matching' do
16
+ mcm = described_class.new(g)
17
+ m = [nil, nil, 3, 2]
18
+ m = mcm.send(:augment, m, [1, 2, 3, 4])
19
+ expect(m).to eq([nil, 2, 1, 4, 3])
20
+ m = mcm.send(:augment, m, [1, 2, 4, 5, 6, 7])
21
+ expect(m).to eq([nil, 2, 1, nil, 5, 4, 7, 6])
22
+ end
23
+ end
24
+
25
+ describe '#match' do
26
+ context 'empty graph' do
27
+ it 'returns empty set' do
28
+ m = described_class.new(g).match
29
+ expect(m).to be_empty
30
+ end
31
+ end
32
+
33
+ context 'single vertex' do
34
+ it 'returns empty set' do
35
+ g.add_vertex(0)
36
+ expect(described_class.new(g).match).to be_empty
37
+ end
38
+ end
39
+
40
+ context 'single edge' do
41
+ it 'returns set with one edge' do
42
+ e = [1, 2]
43
+ g.add_edge(*e)
44
+ m = described_class.new(g).match
45
+ expect(m.size).to eq(1)
46
+ expect(m.edge?(e)).to eq(true)
47
+ end
48
+ end
49
+
50
+ context 'complete bigraph with four vertexes' do
51
+ let(:edges) { [[1, 3], [1, 4], [2, 3], [2, 4]] }
52
+
53
+ it 'returns one of the two correct results' do
54
+ edges.each { |e| g.add_edge(*e) }
55
+ m = described_class.new(g).match
56
+ expect(m.size).to eq(2)
57
+ outcomes = [
58
+ RGL::AdjacencyGraph[1, 3, 2, 4],
59
+ RGL::AdjacencyGraph[1, 4, 2, 3]
60
+ ]
61
+ reconstructed = RGL::AdjacencyGraph.new
62
+ m.to_a.each { |edge| reconstructed.add_edge(*edge) }
63
+ expect(outcomes).to include(reconstructed)
64
+ end
65
+ end
66
+
67
+ # The following example is by Derrick Stolee
68
+ # http://www.youtube.com/watch?v=C9c8zEZXboA
69
+ context 'incomplete bigraph with twelve vertexes' do
70
+ let(:edges) {
71
+ [
72
+ [1, 8],
73
+ [2, 9], [2, 10],
74
+ [3, 7], [3, 9], [3, 12],
75
+ [4, 8], [4, 10],
76
+ [5, 10], [5, 11],
77
+ [6, 11]
78
+ ]
79
+ }
80
+
81
+ it 'returns one of the five correct results' do
82
+ edges.each { |e| g.add_edge(*e) }
83
+ m = described_class.new(g).match
84
+ expect(m.size).to eq(5)
85
+ outcomes = [
86
+ RGL::AdjacencyGraph[1, 8, 2, 9, 3, 7, 5, 10, 6, 11],
87
+ RGL::AdjacencyGraph[1, 8, 2, 9, 3, 7, 4, 10, 5, 11],
88
+ RGL::AdjacencyGraph[1, 8, 2, 9, 3, 7, 4, 10, 6, 11],
89
+ RGL::AdjacencyGraph[1, 8, 2, 9, 3, 12, 4, 10, 5, 11],
90
+ RGL::AdjacencyGraph[2, 9, 3, 7, 4, 8, 5, 10, 6, 11]
91
+ ]
92
+ reconstructed = RGL::AdjacencyGraph.new
93
+ m.to_a.each { |edge| reconstructed.add_edge(*edge) }
94
+ expect(outcomes).to include(reconstructed)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,159 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe GraphMatching::Algorithm::MCMGeneral do
6
+ let(:graph_class) { GraphMatching::Graph::Graph }
7
+ let(:g) { graph_class.new }
8
+
9
+ describe '.new' do
10
+ it 'requires a Graph' do
11
+ expect { described_class.new("banana") }.to raise_error(TypeError)
12
+ end
13
+ end
14
+
15
+ describe '#match' do
16
+ def complete_graph(n)
17
+ g = graph_class.new
18
+ 1.upto(n - 1) do |i|
19
+ (i + 1).upto(n) do |j|
20
+ g.add_edge(i, j)
21
+ end
22
+ end
23
+ g
24
+ end
25
+
26
+ context 'empty graph' do
27
+ it 'returns empty set' do
28
+ expect(described_class.new(g).match).to be_empty
29
+ end
30
+ end
31
+
32
+ context 'single vertex' do
33
+ it 'returns empty set' do
34
+ g.add_vertex(1)
35
+ expect(described_class.new(g).match).to be_empty
36
+ end
37
+ end
38
+
39
+ context 'two vertexes' do
40
+ let(:g) { graph_class[1, 2] }
41
+
42
+ it 'returns one edge' do
43
+ m = described_class.new(g).match
44
+ expect(m.size).to eq(1)
45
+ expect(m).to match_edges [[1, 2]]
46
+ end
47
+ end
48
+
49
+ context 'complete graph with four vertexes' do
50
+ it 'returns two disjoint edges' do
51
+ g = graph_class[
52
+ 1, 2,
53
+ 1, 3,
54
+ 1, 4,
55
+ 2, 3,
56
+ 2, 4,
57
+ 3, 4
58
+ ]
59
+ m = described_class.new(g).match
60
+ expect(m.size).to eq(2)
61
+ expect(m.vertexes).to match_array([1, 2, 3, 4])
62
+ end
63
+ end
64
+
65
+ context 'graph with stem (123) and blossom (456)' do
66
+ it 'returns an expected result' do
67
+ g = graph_class[
68
+ 1, 2,
69
+ 2, 3,
70
+ 3, 4,
71
+ 4, 5,
72
+ 5, 6,
73
+ 6, 4
74
+ ]
75
+ m = described_class.new(g).match
76
+ expect(m.size).to eq(3)
77
+ expect(m).to match_edges [[1, 2], [3, 4], [5, 6]]
78
+ end
79
+ end
80
+
81
+ # TODO: Other algorithms (e.g. both MWM) support disconnected
82
+ # graphs. MCM General should too.
83
+ context 'disconnected graph' do
84
+ it 'raises a DisconnectedGraph error' do
85
+ 2.times { g.add_vertex(double) }
86
+ expect { described_class.new(g).match }.to \
87
+ raise_error(GraphMatching::DisconnectedGraph)
88
+ end
89
+ end
90
+
91
+ it 'simple example: graph with blossom (234)' do
92
+ g = graph_class[
93
+ 1, 2,
94
+ 2, 3,
95
+ 2, 4,
96
+ 3, 4,
97
+ 4, 5,
98
+ 5, 6
99
+ ]
100
+ m = described_class.new(g).match
101
+ expect(m.size).to eq(3)
102
+ expect(m).to match_edges [[1, 2], [3, 4], [5, 6]]
103
+ end
104
+
105
+ it 'example from West\'s Introduction to Graph Theory, p. 143' do
106
+ g = graph_class[
107
+ 1, 2,
108
+ 1, 8,
109
+ 2, 3,
110
+ 3, 4,
111
+ 3, 7,
112
+ 4, 5,
113
+ 4, 7,
114
+ 5, 6,
115
+ 7, 9,
116
+ 8, 9,
117
+ 10, 8
118
+ ]
119
+ m = described_class.new(g).match
120
+ expect(m.size).to eq(5)
121
+ expect(m).to match_edges [[1, 2], [3, 4], [5, 6], [7, 9], [8, 10]]
122
+ end
123
+
124
+ it 'example from Gabow (1976)' do
125
+ g = graph_class[
126
+ 1, 2,
127
+ 2, 3,
128
+ 1, 3,
129
+ 1, 10,
130
+ 3, 9,
131
+ 3, 4,
132
+ 4, 7,
133
+ 4, 8,
134
+ 7, 8,
135
+ 9, 5,
136
+ 5, 6,
137
+ 6, 7
138
+ ]
139
+ m = described_class.new(g).match
140
+ expect(m).to match_edges [[10, 1], [2, 3], [4, 8], [7, 6], [5, 9]]
141
+ end
142
+
143
+ it 'various complete graphs' do
144
+ [
145
+ [1, 0], # size of graph, expected size of matching
146
+ [2, 1],
147
+ [3, 1],
148
+ [4, 2],
149
+ [5, 2],
150
+ [6, 3],
151
+ [20, 10]
152
+ ].each do |test|
153
+ g = complete_graph(test[0])
154
+ m = described_class.new(g).match
155
+ expect(m.size).to eq(test[1])
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,82 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe GraphMatching::Algorithm::MWMBipartite do
6
+ let(:graph_class) { GraphMatching::Graph::WeightedBigraph }
7
+
8
+ describe '.new' do
9
+ it 'requires a WeightedBigraph' do
10
+ expect { described_class.new("banana") }.to raise_error(TypeError)
11
+ end
12
+ end
13
+
14
+ describe '#match' do
15
+ context 'empty graph' do
16
+ it 'returns the expected matching' do
17
+ g = graph_class.new
18
+ m = described_class.new(g).match
19
+ expect(m.size).to eq(0)
20
+ expect(m.weight(g)).to eq(0)
21
+ end
22
+ end
23
+
24
+ context 'trivial bigraph with two vertexes' do
25
+ it 'returns the expected matching' do
26
+ g = graph_class[[1, 2, 7]]
27
+ m = described_class.new(g).match
28
+ expect(m.size).to eq(1)
29
+ expect(m).to match_edges [[1, 2]]
30
+ expect(m.weight(g)).to eq(7)
31
+ end
32
+ end
33
+
34
+ context 'complete bigraph with three vertexes' do
35
+ it 'returns the expected matching' do
36
+ g = graph_class[
37
+ [1, 2, 1],
38
+ [1, 3, 2]
39
+ ]
40
+ m = described_class.new(g).match
41
+ expect(m).to match_edges [[1, 3]]
42
+ expect(m.weight(g)).to eq(2)
43
+ end
44
+
45
+ it 'supports negative weights' do
46
+ g = graph_class[
47
+ [1, 2, -1],
48
+ [1, 3, -3]
49
+ ]
50
+ m = described_class.new(g).match
51
+ expect(m).to match_edges [[1, 2]]
52
+ expect(m.weight(g)).to eq(-1)
53
+ end
54
+ end
55
+
56
+ context 'bigraph with two connected components' do
57
+ it 'returns one of two expected matchings' do
58
+ g = graph_class[
59
+ [1, 5, 3],
60
+ [2, 4, 2],
61
+ [2, 6, 2],
62
+ [3, 5, 3]
63
+ ]
64
+ m = described_class.new(g).match
65
+ expect(m.size).to eq(2)
66
+ expect(m.weight(g)).to eq(5)
67
+ end
68
+
69
+ it 'returns the expected matching' do
70
+ g = graph_class[
71
+ [1, 5, 4],
72
+ [2, 4, 2],
73
+ [2, 6, 1],
74
+ [3, 5, 3]
75
+ ]
76
+ m = described_class.new(g).match
77
+ expect(m).to match_edges [[1, 5], [2, 4]]
78
+ expect(m.weight(g)).to eq(6)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,439 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe GraphMatching::Algorithm::MWMGeneral do
6
+ let(:graph_class) { GraphMatching::Graph::WeightedGraph }
7
+
8
+ describe '.new' do
9
+ it 'requires a WeightedGraph' do
10
+ expect { described_class.new("banana") }.to raise_error(TypeError)
11
+ end
12
+ end
13
+
14
+ describe '#blossom_leaves' do
15
+ context 'five vertexes, one blossom' do
16
+ it 'returns array of leaves' do
17
+ g = graph_class[
18
+ [0, 1, 0],
19
+ [1, 2, 0],
20
+ [1, 3, 0],
21
+ [2, 3, 0],
22
+ [3, 4, 0]
23
+ ]
24
+ a = described_class.new(g)
25
+ a.instance_variable_set(:@blossom_children, [
26
+ nil,
27
+ nil,
28
+ nil,
29
+ nil,
30
+ nil,
31
+ [2, 3, 4]
32
+ ])
33
+ expect(a.send(:blossom_leaves, 0)).to eq([0])
34
+ expect(a.send(:blossom_leaves, 4)).to eq([4])
35
+ expect(a.send(:blossom_leaves, 5)).to eq([2, 3, 4])
36
+ end
37
+ end
38
+ end
39
+
40
+ describe '#match' do
41
+ context 'empty graph' do
42
+ it 'returns empty matching' do
43
+ g = graph_class.new
44
+ m = described_class.new(g).match(true)
45
+ expect(m).to be_empty
46
+ end
47
+ end
48
+
49
+ context 'single vertex' do
50
+ it 'returns empty matching' do
51
+ g = graph_class.new
52
+ g.add_vertex(0)
53
+ m = described_class.new(g).match(true)
54
+ expect(m).to be_empty
55
+ end
56
+ end
57
+
58
+ context 'two vertexes, but no edge' do
59
+ it 'returns empty matching' do
60
+ g = graph_class.new
61
+ g.add_vertex(0)
62
+ g.add_vertex(1)
63
+ m = described_class.new(g).match(true)
64
+ expect(m).to be_empty
65
+ end
66
+ end
67
+
68
+ context 'two vertexes' do
69
+ it 'returns matching of size 1' do
70
+ g = graph_class[[0, 1, 7]]
71
+ m = described_class.new(g).match(true)
72
+ expect(m).to match_edges [[0, 1]]
73
+ expect(m.weight(g)).to eq(7)
74
+ end
75
+ end
76
+
77
+ context 'three vertexes' do
78
+ it 'matches the edge with greater weight' do
79
+ g = graph_class[
80
+ [0, 1, 1],
81
+ [1, 2, 2],
82
+ [2, 0, 3]
83
+ ]
84
+ m = described_class.new(g).match(true)
85
+ expect(m).to match_edges [[0, 2]]
86
+ expect(m.weight(g)).to eq(3)
87
+ end
88
+ end
89
+
90
+ context 'greatest weight edge cannot be used in complete matching' do
91
+ it 'returns the complete matching with max. weight' do
92
+ # In the following graph, edge 1=3 has the greatest weight,
93
+ # but that edge cannot be used in a complete matching.
94
+ g = graph_class[
95
+ [0, 1, 2],
96
+ [1, 2, 0],
97
+ [1, 3, 6],
98
+ [2, 3, 4],
99
+ [3, 4, 2]
100
+ ]
101
+ m = described_class.new(g).match(true)
102
+ expect(m).to match_edges [[0, 1], [2, 3]]
103
+ expect(m.weight(g)).to eq(6)
104
+ end
105
+ end
106
+
107
+ it "passes Van Rantwijk test 12" do
108
+ g = graph_class[[1, 2, 10], [2, 3, 11]]
109
+ m = described_class.new(g).match(false)
110
+ expect(m).to match_edges [[2, 3]]
111
+ expect(m.weight(g)).to eq(11)
112
+ end
113
+
114
+ it "passes Van Rantwijk test 13" do
115
+ g = graph_class[
116
+ [1, 2, 5],
117
+ [2, 3, 11],
118
+ [3, 4, 5]
119
+ ]
120
+ m = described_class.new(g).match(false)
121
+ expect(m).to match_edges [[2, 3]]
122
+ expect(m.weight(g)).to eq(11)
123
+ end
124
+
125
+ it "passes Van Rantwijk test 14: max. cardinality" do
126
+ g = graph_class[
127
+ [1, 2, 5],
128
+ [2, 3, 11],
129
+ [3, 4, 5]
130
+ ]
131
+ m = described_class.new(g).match(true)
132
+ expect(m).to match_edges [[1, 2], [3, 4]]
133
+ expect(m.weight(g)).to eq(10)
134
+ end
135
+
136
+ it "passes Van Rantwijk test 15: floating-point weights" do
137
+ g = graph_class[
138
+ [1, 2, Math::PI],
139
+ [2, 3, Math.exp(1)],
140
+ [1, 3, 3.0],
141
+ [1, 4, Math.sqrt(2.0)]
142
+ ]
143
+ m = described_class.new(g).match(false)
144
+ expect(m).to match_edges [[1, 4], [2, 3]]
145
+ expect(m.weight(g)).to be_within(0.00001).of(Math.sqrt(2.0) + Math.exp(1))
146
+ end
147
+
148
+ it "passes Van Rantwijk test 16: negative weights" do
149
+ g = graph_class[
150
+ [1, 2, 2],
151
+ [1, 3, -2],
152
+ [2, 3, 1],
153
+ [2, 4, -1],
154
+ [3, 4, -6]
155
+ ]
156
+ m = described_class.new(g).match(false)
157
+ expect(m).to match_edges [[1, 2]]
158
+ expect(m.weight(g)).to eq(2)
159
+ m = described_class.new(g).match(true)
160
+ expect(m).to match_edges [[1, 3], [2, 4]]
161
+ expect(m.weight(g)).to eq(-3)
162
+ end
163
+
164
+ context "Van Rantwijk test 20: Uses S-blossom for augmentation" do
165
+ it "passes test 20-A" do
166
+ g = graph_class[
167
+ [1, 2, 8],
168
+ [1, 3, 9],
169
+ [2, 3, 10],
170
+ [3, 4, 7]
171
+ ]
172
+ m = described_class.new(g).match(false)
173
+ expect(m).to match_edges [[1, 2], [3, 4]]
174
+ expect(m.weight(g)).to eq(15)
175
+ end
176
+
177
+ it "passes test 20-B" do
178
+ g = graph_class[
179
+ [1, 2, 8],
180
+ [1, 3, 9],
181
+ [2, 3, 10],
182
+ [3, 4, 7],
183
+ [1, 6, 5],
184
+ [4, 5, 6]
185
+ ]
186
+ m = described_class.new(g).match(false)
187
+ expect(m).to match_edges [[1, 6], [2, 3], [4, 5]]
188
+ expect(m.weight(g)).to eq(21)
189
+ end
190
+ end
191
+
192
+ # Van Rantwijk test 21
193
+ context "create S-blossom, relabel as T-blossom, use for augmentation" do
194
+ it "passes test 21-A" do
195
+ g = graph_class[
196
+ [1, 2, 9],
197
+ [1, 3, 8],
198
+ [2, 3, 10],
199
+ [1, 4, 5],
200
+ [4, 5, 4],
201
+ [1, 6, 3]
202
+ ]
203
+ m = described_class.new(g).match(false)
204
+ expect(m).to match_edges [[1, 6], [2, 3], [4, 5]]
205
+ end
206
+
207
+ it "passes test 21-B" do
208
+ g = graph_class[
209
+ [1, 2, 9],
210
+ [1, 3, 8],
211
+ [2, 3, 10],
212
+ [1, 4, 5],
213
+ [4, 5, 3],
214
+ [1, 6, 4]
215
+ ]
216
+ m = described_class.new(g).match(false)
217
+ expect(m).to match_edges [[1, 6], [2, 3], [4, 5]]
218
+ end
219
+
220
+ it "passes test 21-C" do
221
+ g = graph_class[
222
+ [1, 2, 9],
223
+ [1, 3, 8],
224
+ [2, 3, 10],
225
+ [1, 4, 5],
226
+ [4, 5, 3],
227
+ [3, 6, 4]
228
+ ]
229
+ m = described_class.new(g).match(false)
230
+ expect(m).to match_edges [[1, 2], [3, 6], [4, 5]]
231
+ end
232
+ end
233
+
234
+ context "Van Rantwijk test 22" do
235
+ it "creates nested S-blossom, uses for augmentation" do
236
+ g = graph_class[
237
+ [1, 2, 9],
238
+ [1, 3, 9],
239
+ [2, 3, 10],
240
+ [2, 4, 8],
241
+ [3, 5, 8],
242
+ [4, 5, 10],
243
+ [5, 6, 6]
244
+ ]
245
+ m = described_class.new(g).match(false)
246
+ expect(m).to match_edges [[1, 3], [2, 4], [5, 6]]
247
+ end
248
+ end
249
+
250
+ context "Van Rantwijk test 23" do
251
+ it "create S-blossom, relabel as S, include in nested S-blossom" do
252
+ g = graph_class[
253
+ [1, 2, 10],
254
+ [1, 7, 10],
255
+ [2, 3, 12],
256
+ [3, 4, 20],
257
+ [3, 5, 20],
258
+ [4, 5, 25],
259
+ [5, 6, 10],
260
+ [6, 7, 10],
261
+ [7, 8, 8]
262
+ ]
263
+ m = described_class.new(g).match(false)
264
+ expect(m).to match_edges [[1, 2], [3, 4], [5, 6], [7, 8]]
265
+ end
266
+ end
267
+
268
+ context "Van Rantwijk test 24" do
269
+ it "create nested S-blossom, augment, expand recursively" do
270
+ g = graph_class[
271
+ [1, 2, 8],
272
+ [1, 3, 8],
273
+ [2, 3, 10],
274
+ [2, 4, 12],
275
+ [3, 5, 12],
276
+ [4, 5, 14],
277
+ [4, 6, 12],
278
+ [5, 7, 12],
279
+ [6, 7, 14],
280
+ [7, 8, 12]
281
+ ]
282
+ m = described_class.new(g).match(false)
283
+ expect(m).to match_edges [[1, 2], [3, 5], [4, 6], [7, 8]]
284
+ end
285
+ end
286
+
287
+ context "Van Rantwijk test 25" do
288
+ it "create S-blossom, relabel as T, expand" do
289
+ g = graph_class[
290
+ [1, 2, 23],
291
+ [1, 5, 22],
292
+ [1, 6, 15],
293
+ [2, 3, 25],
294
+ [3, 4, 22],
295
+ [4, 5, 25],
296
+ [4, 8, 14],
297
+ [5, 7, 13]
298
+ ]
299
+ m = described_class.new(g).match(false)
300
+ expect(m).to match_edges [[1, 6], [2, 3], [4, 8], [5, 7]]
301
+ end
302
+ end
303
+
304
+ context "Van Rantwijk test 26" do
305
+ it "create nested S-blossom, relabel as T, expand" do
306
+ g = graph_class[
307
+ [1, 2, 19],
308
+ [1, 3, 20],
309
+ [1, 8, 8],
310
+ [2, 3, 25],
311
+ [2, 4, 18],
312
+ [3, 5, 18],
313
+ [4, 5, 13],
314
+ [4, 7, 7],
315
+ [5, 6, 7]
316
+ ]
317
+ m = described_class.new(g).match(false)
318
+ expect(m).to match_edges [[1, 8], [2, 3], [4, 7], [5, 6]]
319
+ end
320
+ end
321
+
322
+ context "Van Rantwijk test 30" do
323
+ it "create blossom, relabel as T in more than one way, expand, augment" do
324
+ g = graph_class[
325
+ [1, 2, 45],
326
+ [1, 5, 45],
327
+ [2, 3, 50],
328
+ [3, 4, 45],
329
+ [4, 5, 50],
330
+ [1, 6, 30],
331
+ [3, 9, 35],
332
+ [4, 8, 35],
333
+ [5, 7, 26],
334
+ [9, 10, 5]
335
+ ]
336
+ m = described_class.new(g).match(false)
337
+ expect(m).to match_edges [[1, 6], [2, 3], [4, 8], [5, 7], [9, 10]]
338
+ end
339
+ end
340
+
341
+ context "Van Rantwijk test 31" do
342
+ it "similar to test 30, but slightly different" do
343
+ g = graph_class[
344
+ [1, 2, 45],
345
+ [1, 5, 45],
346
+ [2, 3, 50],
347
+ [3, 4, 45],
348
+ [4, 5, 50],
349
+ [1, 6, 30],
350
+ [3, 9, 35],
351
+ [4, 8, 26], # differs from test 30
352
+ [5, 7, 40], # differs from test 30
353
+ [9, 10, 5]
354
+ ]
355
+ m = described_class.new(g).match(false)
356
+ expect(m).to match_edges [[1, 6], [2, 3], [4, 8], [5, 7], [9, 10]]
357
+ end
358
+ end
359
+
360
+ context "Van Rantwijk test 32" do
361
+ # create blossom, relabel as T, expand such that a new
362
+ # least-slack S-to-free edge is produced, augment
363
+ it "see comment" do
364
+ g = graph_class[
365
+ [1, 2, 45],
366
+ [1, 5, 45],
367
+ [2, 3, 50],
368
+ [3, 4, 45],
369
+ [4, 5, 50],
370
+ [1, 6, 30],
371
+ [3, 9, 35],
372
+ [4, 8, 28],
373
+ [5, 7, 26],
374
+ [9, 10, 5]
375
+ ]
376
+ m = described_class.new(g).match(false)
377
+ expect(m).to match_edges [[1, 6], [2, 3], [4, 8], [5, 7], [9, 10]]
378
+ end
379
+ end
380
+
381
+ context "Van Rantwijk test 33" do
382
+ # create nested blossom, relabel as T in more than one way,
383
+ # expand outer blossom such that inner blossom ends up on
384
+ # an augmenting path
385
+ it "see comment" do
386
+ g = graph_class[
387
+ [1, 2, 45],
388
+ [1, 7, 45],
389
+ [2, 3, 50],
390
+ [3, 4, 45],
391
+ [4, 5, 95],
392
+ [4, 6, 94],
393
+ [5, 6, 94],
394
+ [6, 7, 50],
395
+ [1, 8, 30],
396
+ [3, 11, 35],
397
+ [5, 9, 36],
398
+ [7, 10, 26],
399
+ [11, 12, 5]
400
+ ]
401
+ m = described_class.new(g).match(false)
402
+ expect(m).to match_edges [
403
+ [1, 8],
404
+ [2, 3],
405
+ [4, 6],
406
+ [5, 9],
407
+ [7, 10],
408
+ [11, 12]
409
+ ]
410
+ end
411
+ end
412
+
413
+ context "Van Rantwijk test 34: nest, relabel, expand" do
414
+ it "create nested S-blossom, relabel as S, expand recursively" do
415
+ g = graph_class[
416
+ [1, 2, 40],
417
+ [1, 3, 40],
418
+ [2, 3, 60],
419
+ [2, 4, 55],
420
+ [3, 5, 55],
421
+ [4, 5, 50],
422
+ [1, 8, 15],
423
+ [5, 7, 30],
424
+ [7, 6, 10],
425
+ [8, 10, 10],
426
+ [4, 9, 30]
427
+ ]
428
+ m = described_class.new(g).match(false)
429
+ expect(m).to match_edges [
430
+ [1, 2],
431
+ [3, 5],
432
+ [4, 9],
433
+ [6, 7],
434
+ [8, 10]
435
+ ]
436
+ end
437
+ end
438
+ end
439
+ end