graph_matching 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +35 -0
- data/.rubocop_todo.yml +29 -256
- data/CHANGELOG.md +8 -2
- data/Rakefile +3 -3
- data/benchmark/mcm_bipartite/complete_bigraphs/benchmark.rb +1 -1
- data/benchmark/mcm_general/complete_graphs/benchmark.rb +1 -1
- data/benchmark/mwm_bipartite/complete_bigraphs/benchmark.rb +1 -1
- data/benchmark/mwm_bipartite/misc/calc_d2/benchmark.rb +1 -5
- data/benchmark/mwm_general/complete_graphs/benchmark.rb +2 -2
- data/benchmark/mwm_general/incomplete_graphs/benchmark.rb +6 -7
- data/graph_matching.gemspec +7 -11
- data/lib/graph_matching/algorithm/mcm_bipartite.rb +9 -8
- data/lib/graph_matching/algorithm/mcm_general.rb +2 -2
- data/lib/graph_matching/algorithm/mwm_bipartite.rb +25 -25
- data/lib/graph_matching/algorithm/mwm_general.rb +121 -117
- data/lib/graph_matching/algorithm/mwmg_delta_assertions.rb +47 -50
- data/lib/graph_matching/assertion.rb +4 -4
- data/lib/graph_matching/core_ext/set.rb +1 -1
- data/lib/graph_matching/graph/bigraph.rb +1 -1
- data/lib/graph_matching/graph/graph.rb +1 -1
- data/lib/graph_matching/graph/weighted.rb +11 -8
- data/lib/graph_matching/integer_vertexes.rb +2 -2
- data/lib/graph_matching/version.rb +1 -1
- data/lib/graph_matching/visualize.rb +5 -5
- data/profile/mwm_general/profile.rb +1 -1
- data/spec/graph_matching/algorithm/mcm_bipartite_spec.rb +1 -1
- data/spec/graph_matching/algorithm/mcm_general_spec.rb +1 -1
- data/spec/graph_matching/algorithm/mwm_bipartite_spec.rb +1 -1
- data/spec/graph_matching/algorithm/mwm_general_spec.rb +44 -41
- data/spec/graph_matching/graph/graph_spec.rb +4 -4
- data/spec/graph_matching/integer_vertexes_spec.rb +2 -2
- data/spec/spec_helper.rb +2 -2
- metadata +17 -17
@@ -13,7 +13,7 @@ $stdout.sync = true
|
|
13
13
|
|
14
14
|
def complete_graph(n)
|
15
15
|
g = GraphMatching::Graph::WeightedGraph.new
|
16
|
-
n_edges = (1
|
16
|
+
n_edges = (1..n - 1).reduce(:+)
|
17
17
|
0.upto(n - 2) do |i|
|
18
18
|
(i + 1).upto(n - 1) do |j|
|
19
19
|
g.add_edge(i, j)
|
@@ -27,6 +27,6 @@ MIN_SIZE.upto(MAX_SIZE) do |v|
|
|
27
27
|
print "%5d\t" % [v]
|
28
28
|
g = complete_graph(v)
|
29
29
|
GC.disable
|
30
|
-
puts
|
30
|
+
puts(Benchmark.realtime { g.maximum_weighted_matching(true) })
|
31
31
|
GC.enable
|
32
32
|
end
|
@@ -17,14 +17,13 @@ $stdout.sync = true
|
|
17
17
|
def incomplete_graph(n, completeness)
|
18
18
|
g = GraphMatching::Graph::WeightedGraph.new
|
19
19
|
0.upto(n - 1) do |i| g.add_vertex(i) end
|
20
|
-
max_weight = ((1
|
20
|
+
max_weight = ((1..n - 1).reduce(:+).to_f * completeness).to_i + 1
|
21
21
|
0.upto(n - 2) do |i|
|
22
22
|
(i + 1).upto(n - 1) do |j|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
23
|
+
next unless rand < completeness
|
24
|
+
g.add_edge(i, j)
|
25
|
+
w = rand(max_weight)
|
26
|
+
g.set_w([i, j], w)
|
28
27
|
end
|
29
28
|
end
|
30
29
|
g
|
@@ -34,6 +33,6 @@ MIN_SIZE.upto(MAX_SIZE) do |v|
|
|
34
33
|
print "%5d\t" % [v]
|
35
34
|
g = incomplete_graph(v, COMPLETENESS)
|
36
35
|
GC.disable
|
37
|
-
puts
|
36
|
+
puts(Benchmark.realtime { g.maximum_weighted_matching(true) })
|
38
37
|
GC.enable
|
39
38
|
end
|
data/graph_matching.gemspec
CHANGED
@@ -15,21 +15,17 @@ Gem::Specification.new do |spec|
|
|
15
15
|
EOS
|
16
16
|
spec.homepage = 'https://github.com/jaredbeck/graph_matching'
|
17
17
|
spec.license = 'MIT'
|
18
|
-
|
19
18
|
spec.files = `git ls-files -z`.split("\x0")
|
20
19
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
21
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
22
21
|
spec.require_paths = ['lib']
|
23
|
-
|
24
|
-
spec.required_ruby_version = '>= 2.0.0'
|
25
|
-
|
22
|
+
spec.required_ruby_version = '>= 2.2.0'
|
26
23
|
spec.add_runtime_dependency 'rgl', '~> 0.5.0'
|
27
|
-
|
28
24
|
spec.add_development_dependency 'bundler', '~> 1.12'
|
29
|
-
spec.add_development_dependency 'pry-
|
30
|
-
spec.add_development_dependency 'rspec-core', '~> 3.
|
31
|
-
spec.add_development_dependency 'rspec-expectations', '~> 3.
|
32
|
-
spec.add_development_dependency 'rspec-mocks', '~> 3.
|
33
|
-
spec.add_development_dependency 'rake', '~>
|
34
|
-
spec.add_development_dependency 'rubocop', '~> 0.
|
25
|
+
spec.add_development_dependency 'pry-byebug', '~> 3.4'
|
26
|
+
spec.add_development_dependency 'rspec-core', '~> 3.6'
|
27
|
+
spec.add_development_dependency 'rspec-expectations', '~> 3.6'
|
28
|
+
spec.add_development_dependency 'rspec-mocks', '~> 3.6'
|
29
|
+
spec.add_development_dependency 'rake', '~> 12.0'
|
30
|
+
spec.add_development_dependency 'rubocop', '~> 0.48.1'
|
35
31
|
end
|
@@ -9,18 +9,16 @@ module GraphMatching
|
|
9
9
|
# `MCMBipartite` implements Maximum Cardinality Matching in
|
10
10
|
# bipartite graphs.
|
11
11
|
class MCMBipartite < MatchingAlgorithm
|
12
|
-
|
13
12
|
def initialize(graph)
|
14
13
|
assert(graph).is_a(Graph::Bigraph)
|
15
14
|
super
|
16
15
|
end
|
17
16
|
|
18
17
|
def match
|
19
|
-
u
|
18
|
+
u = g.partition[0]
|
20
19
|
m = []
|
21
20
|
|
22
|
-
|
23
|
-
|
21
|
+
loop do
|
24
22
|
# Begin each stage by clearing all labels and marks
|
25
23
|
t = []
|
26
24
|
predecessors = {}
|
@@ -31,11 +29,14 @@ module GraphMatching
|
|
31
29
|
unmarked = r = u.select { |i| m[i] == nil }
|
32
30
|
|
33
31
|
# While there are unmarked R-vertexes
|
34
|
-
|
32
|
+
loop do
|
33
|
+
break unless aug_path.nil?
|
34
|
+
start = unmarked.pop
|
35
|
+
break unless start
|
35
36
|
|
36
37
|
# Follow the unmatched edges (if any) to vertexes in V
|
37
38
|
# ignoring any V-vertexes already labeled T
|
38
|
-
unlabeled_across_unmatched_edges_from(start, g, m
|
39
|
+
unlabeled_across_unmatched_edges_from(start, g, m, t).each do |vi|
|
39
40
|
t << vi
|
40
41
|
predecessors[vi] = start
|
41
42
|
|
@@ -70,7 +71,7 @@ module GraphMatching
|
|
70
71
|
|
71
72
|
def assert_valid_aug_path(path)
|
72
73
|
unless path.length >= 2 && path.length.even?
|
73
|
-
raise
|
74
|
+
raise 'Invalid augmenting path'
|
74
75
|
end
|
75
76
|
end
|
76
77
|
|
@@ -93,7 +94,7 @@ module GraphMatching
|
|
93
94
|
|
94
95
|
def backtrack_from(end_vertex, predecessors)
|
95
96
|
augmenting_path = [end_vertex]
|
96
|
-
while predecessors.
|
97
|
+
while predecessors.key?(augmenting_path.last)
|
97
98
|
augmenting_path.push(predecessors[augmenting_path.last])
|
98
99
|
end
|
99
100
|
augmenting_path
|
@@ -57,7 +57,7 @@ module GraphMatching
|
|
57
57
|
# assign a start label and begin a new search)
|
58
58
|
# set LABEL(u) = FIRST(u) = 0
|
59
59
|
|
60
|
-
|
60
|
+
loop do
|
61
61
|
u += 1
|
62
62
|
break if u > g.size
|
63
63
|
if mate[u] != 0
|
@@ -275,7 +275,7 @@ module GraphMatching
|
|
275
275
|
r(x, y, label, mate)
|
276
276
|
r(y, x, label, mate)
|
277
277
|
else
|
278
|
-
|
278
|
+
raise "Vertex #{v} has an unexpected label type"
|
279
279
|
end
|
280
280
|
end
|
281
281
|
|
@@ -28,8 +28,7 @@ module GraphMatching
|
|
28
28
|
u = init_duals(cats, dogs)
|
29
29
|
|
30
30
|
# For each stage
|
31
|
-
|
32
|
-
|
31
|
+
loop do
|
33
32
|
# Clear all labels and marks
|
34
33
|
# Label all single dogs with S
|
35
34
|
aug_path = nil
|
@@ -39,29 +38,31 @@ module GraphMatching
|
|
39
38
|
q = s.dup.to_a
|
40
39
|
|
41
40
|
# While searching
|
42
|
-
|
41
|
+
loop do
|
42
|
+
break unless aug_path.nil?
|
43
|
+
i = q.pop
|
44
|
+
break unless i
|
43
45
|
|
44
46
|
# Follow the unmatched edges (if any) to free (unlabeled)
|
45
47
|
# cats. Only consider edges with slack (π) of 0.
|
46
|
-
unlabeled_across_unmatched_edges_from(i, g, m
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
48
|
+
unlabeled_across_unmatched_edges_from(i, g, m, t).each do |j|
|
49
|
+
next unless slack(u, i, j) == 0
|
50
|
+
t << j
|
51
|
+
predecessors[j] = i
|
52
|
+
|
53
|
+
# If there are matched edges, follow each to a dog and
|
54
|
+
# label the dog with S. Otherwise, backtrack to
|
55
|
+
# construct an augmenting path.
|
56
|
+
m_dogs = matched_adjacent(j, i, g, m)
|
57
|
+
|
58
|
+
m_dogs.each do |md|
|
59
|
+
s << md
|
60
|
+
predecessors[md] = j
|
61
|
+
end
|
62
|
+
|
63
|
+
if m_dogs.empty?
|
64
|
+
aug_path = backtrack_from(j, predecessors)
|
65
|
+
break
|
65
66
|
end
|
66
67
|
end
|
67
68
|
end
|
@@ -116,7 +117,7 @@ module GraphMatching
|
|
116
117
|
s.each do |s_dog|
|
117
118
|
g.each_adjacent(s_dog) do |cat|
|
118
119
|
unless t.include?(cat)
|
119
|
-
slacks.push
|
120
|
+
slacks.push slack(u, s_dog, cat)
|
120
121
|
end
|
121
122
|
end
|
122
123
|
end
|
@@ -138,10 +139,9 @@ module GraphMatching
|
|
138
139
|
|
139
140
|
# Returns the "slack" of an edge (Galil, 1986, p.30), the
|
140
141
|
# difference between an edge's duals and its weight.
|
141
|
-
def
|
142
|
+
def slack(u, i, j)
|
142
143
|
u[i] + u[j] - @weight[i - 1][j - 1]
|
143
144
|
end
|
144
|
-
|
145
145
|
end
|
146
146
|
end
|
147
147
|
end
|
@@ -19,7 +19,7 @@ module GraphMatching
|
|
19
19
|
LBL_S = 1
|
20
20
|
LBL_T = 2
|
21
21
|
LBL_CRUMB = 5
|
22
|
-
LBL_NAMES = %w(Free S T Crumb)
|
22
|
+
LBL_NAMES = %w(Free S T Crumb).freeze
|
23
23
|
|
24
24
|
def initialize(graph)
|
25
25
|
assert(graph).is_a(Graph::WeightedGraph)
|
@@ -42,13 +42,12 @@ module GraphMatching
|
|
42
42
|
|
43
43
|
# Iterative *stages*. Each stage augments the matching.
|
44
44
|
# There can be at most n stages, where n is num. vertexes.
|
45
|
-
|
45
|
+
loop do
|
46
46
|
init_stage
|
47
47
|
|
48
48
|
# *sub-stages* either augment or scale the duals.
|
49
49
|
augmented = false
|
50
|
-
|
51
|
-
|
50
|
+
loop do
|
52
51
|
# > The search is conducted by scanning the S-vertices
|
53
52
|
# > in turn. (Galil, 1986, p. 26)
|
54
53
|
until augmented || @queue.empty?
|
@@ -60,9 +59,9 @@ module GraphMatching
|
|
60
59
|
# > There is no augmenting path under these constraints;
|
61
60
|
# > compute delta and reduce slack in the optimization problem.
|
62
61
|
# > (Van Rantwijk, mwmatching.py, line 732)
|
63
|
-
delta,
|
62
|
+
delta, d_type, d_edge, d_blossom = calc_delta(max_cardinality)
|
64
63
|
update_duals(delta)
|
65
|
-
optimum = act_on_minimum_delta(
|
64
|
+
optimum = act_on_minimum_delta(d_type, d_edge, d_blossom)
|
66
65
|
break if optimum
|
67
66
|
end
|
68
67
|
|
@@ -91,21 +90,23 @@ module GraphMatching
|
|
91
90
|
@tight_edge[delta_edge] = true
|
92
91
|
i, j = @edges[delta_edge].to_a
|
93
92
|
if @label[@in_blossom[i]] == LBL_FREE
|
94
|
-
i
|
93
|
+
# Think of this as swapping i and j, but the corresponding
|
94
|
+
# assignment `j = i` happens to be unnecessary.
|
95
|
+
i = j
|
95
96
|
end
|
96
97
|
assert_label(@in_blossom[i], LBL_S)
|
97
98
|
@queue.push(i)
|
98
99
|
when 3
|
99
100
|
# > Use the least-slack edge to continue the search.
|
100
101
|
@tight_edge[delta_edge] = true
|
101
|
-
i
|
102
|
+
i = @edges[delta_edge].to_a[0]
|
102
103
|
assert_label(@in_blossom[i], LBL_S)
|
103
104
|
@queue.push(i)
|
104
105
|
when 4
|
105
106
|
# > Expand the least-z blossom.
|
106
107
|
expand_blossom(delta_blossom, false)
|
107
108
|
else
|
108
|
-
|
109
|
+
raise "Invalid delta_type: #{delta_type}"
|
109
110
|
end
|
110
111
|
optimum
|
111
112
|
end
|
@@ -138,19 +139,19 @@ module GraphMatching
|
|
138
139
|
#
|
139
140
|
@blossom_children[b] = []
|
140
141
|
@blossom_endps[b] = []
|
141
|
-
trace_to_base(bv, bb) do |
|
142
|
-
@blossom_parent[
|
143
|
-
@blossom_children[b] <<
|
144
|
-
@blossom_endps[b] << @label_end[
|
142
|
+
trace_to_base(bv, bb) do |bv2|
|
143
|
+
@blossom_parent[bv2] = b
|
144
|
+
@blossom_children[b] << bv2
|
145
|
+
@blossom_endps[b] << @label_end[bv2]
|
145
146
|
end
|
146
147
|
@blossom_children[b] << bb
|
147
148
|
@blossom_children[b].reverse!
|
148
149
|
@blossom_endps[b].reverse!
|
149
150
|
@blossom_endps[b] << 2 * k
|
150
|
-
trace_to_base(bw, bb) do |
|
151
|
-
@blossom_parent[
|
152
|
-
@blossom_children[b] <<
|
153
|
-
@blossom_endps[b] << (@label_end[
|
151
|
+
trace_to_base(bw, bb) do |bw2|
|
152
|
+
@blossom_parent[bw2] = b
|
153
|
+
@blossom_children[b] << bw2
|
154
|
+
@blossom_endps[b] << (@label_end[bw2] ^ 1)
|
154
155
|
end
|
155
156
|
|
156
157
|
# > Set label to S
|
@@ -162,58 +163,58 @@ module GraphMatching
|
|
162
163
|
@dual[b] = 0
|
163
164
|
|
164
165
|
# > Relabel vertices.
|
165
|
-
blossom_leaves(b).each do |
|
166
|
-
if @label[@in_blossom[
|
166
|
+
blossom_leaves(b).each do |leaf|
|
167
|
+
if @label[@in_blossom[leaf]] == LBL_T
|
167
168
|
# > This T-vertex now turns into an S-vertex because it
|
168
169
|
# > becomes part of an S-blossom; add it to the queue.
|
169
|
-
@queue <<
|
170
|
+
@queue << leaf
|
170
171
|
end
|
171
|
-
@in_blossom[
|
172
|
+
@in_blossom[leaf] = b
|
172
173
|
end
|
173
174
|
|
174
175
|
# > Compute blossombestedges[b].
|
175
176
|
best_edge_to = rantwijk_array(nil)
|
176
|
-
@blossom_children[b].each do |
|
177
|
-
if @blossom_best_edges[
|
177
|
+
@blossom_children[b].each do |child|
|
178
|
+
if @blossom_best_edges[child].nil?
|
178
179
|
# > This subblossom [bv] does not have a list of least-
|
179
180
|
# > slack edges. Get the information from the vertices.
|
180
|
-
nblists = blossom_leaves(
|
181
|
-
@neighb_end[
|
182
|
-
p / 2 # floor division
|
181
|
+
nblists = blossom_leaves(child).map { |leaf|
|
182
|
+
@neighb_end[leaf].map { |p|
|
183
|
+
p / 2 # Intentional floor division
|
183
184
|
}
|
184
185
|
}
|
185
186
|
else
|
186
187
|
# > Walk this subblossom's least-slack edges.
|
187
|
-
nblists = [@blossom_best_edges[
|
188
|
+
nblists = [@blossom_best_edges[child]]
|
188
189
|
end
|
189
190
|
|
190
191
|
nblists.each do |nblist|
|
191
|
-
nblist.each do |
|
192
|
-
i, j = @edges[
|
192
|
+
nblist.each do |x|
|
193
|
+
i, j = @edges[x].to_a
|
193
194
|
if @in_blossom[j] == b
|
194
|
-
i
|
195
|
+
# Think of this as swapping i and j, but the corresponding
|
196
|
+
# assignment `i = j` happens to be unnecessary.
|
197
|
+
j = i
|
195
198
|
end
|
196
199
|
bj = @in_blossom[j]
|
197
|
-
if bj
|
198
|
-
|
199
|
-
(best_edge_to[bj] == nil || slack(k) < slack(best_edge_to[bj]))
|
200
|
-
best_edge_to[bj] = k
|
200
|
+
if better_edge_to?(bj, x, b, best_edge_to)
|
201
|
+
best_edge_to[bj] = x
|
201
202
|
end
|
202
203
|
end
|
203
204
|
end
|
204
205
|
|
205
206
|
# > Forget about least-slack edges of the subblossom.
|
206
|
-
@blossom_best_edges[
|
207
|
-
@best_edge[
|
207
|
+
@blossom_best_edges[child] = nil
|
208
|
+
@best_edge[child] = nil
|
208
209
|
end
|
209
210
|
|
210
211
|
@blossom_best_edges[b] = best_edge_to.compact
|
211
212
|
|
212
213
|
# > Select bestedge[b]
|
213
214
|
@best_edge[b] = nil
|
214
|
-
@blossom_best_edges[b].each do |
|
215
|
-
if @best_edge[b].nil? || slack(
|
216
|
-
@best_edge[b] =
|
215
|
+
@blossom_best_edges[b].each do |edge|
|
216
|
+
if @best_edge[b].nil? || slack(edge) < slack(@best_edge[b])
|
217
|
+
@best_edge[b] = edge
|
217
218
|
end
|
218
219
|
end
|
219
220
|
end
|
@@ -223,7 +224,7 @@ module GraphMatching
|
|
223
224
|
s = @label[b] == LBL_S
|
224
225
|
m = @label_end[b] == @mate[@blossom_base[b]]
|
225
226
|
unless t || s && m
|
226
|
-
|
227
|
+
raise <<-EOS
|
227
228
|
Assertion failed: Expected either:
|
228
229
|
1. Current Bv to be a T-blossom, or
|
229
230
|
2. Bv is an S-blossom and its base is matched to @label_end[bv]
|
@@ -233,7 +234,7 @@ module GraphMatching
|
|
233
234
|
|
234
235
|
def assert_label(ix, lbl)
|
235
236
|
unless @label[ix] == lbl
|
236
|
-
|
237
|
+
raise "Expected label at #{ix} to be #{LBL_NAMES[lbl]}"
|
237
238
|
end
|
238
239
|
end
|
239
240
|
|
@@ -258,7 +259,7 @@ module GraphMatching
|
|
258
259
|
# with an external mate.)
|
259
260
|
base = @blossom_base[b]
|
260
261
|
if @mate[base].nil?
|
261
|
-
|
262
|
+
raise "Expected blossom #{b}'s base (#{base}) to be matched"
|
262
263
|
end
|
263
264
|
|
264
265
|
# Assign label S to the mate of blossom b's base.
|
@@ -266,9 +267,13 @@ module GraphMatching
|
|
266
267
|
# The bitwise XOR is very clever. `mate[x]` and `mate[x] ^ 1`
|
267
268
|
# are connected "endpoints".
|
268
269
|
base_edge_endpoints = [@mate[base], @mate[base] ^ 1]
|
269
|
-
assign_label(
|
270
|
+
assign_label(
|
271
|
+
@endpoint[base_edge_endpoints[0]],
|
272
|
+
LBL_S,
|
273
|
+
base_edge_endpoints[1]
|
274
|
+
)
|
270
275
|
else
|
271
|
-
|
276
|
+
raise ArgumentError, "Unexpected label: #{t}"
|
272
277
|
end
|
273
278
|
end
|
274
279
|
|
@@ -329,7 +334,7 @@ module GraphMatching
|
|
329
334
|
# > until we find a single vertex, swapping matched and unmatched
|
330
335
|
# > edges as we go.
|
331
336
|
# > (Van Rantwijk, mwmatching.py, line 504)
|
332
|
-
|
337
|
+
loop do
|
333
338
|
bs = @in_blossom[s]
|
334
339
|
assert_label(bs, LBL_S)
|
335
340
|
assert(@label_end[bs]).eq(@mate[@blossom_base[bs]])
|
@@ -361,6 +366,12 @@ module GraphMatching
|
|
361
366
|
end
|
362
367
|
end
|
363
368
|
|
369
|
+
def better_edge_to?(bj, k, b, best_edge_to)
|
370
|
+
bj != b &&
|
371
|
+
@label[bj] == LBL_S &&
|
372
|
+
(best_edge_to[bj] == nil || slack(k) < slack(best_edge_to[bj]))
|
373
|
+
end
|
374
|
+
|
364
375
|
# TODO: Optimize by returning lazy iterator
|
365
376
|
def blossom_leaves(b, ary = [])
|
366
377
|
if b < @nvertex
|
@@ -391,7 +402,7 @@ module GraphMatching
|
|
391
402
|
jstep = -1
|
392
403
|
endptrick = 1
|
393
404
|
end
|
394
|
-
|
405
|
+
[j, jstep, endptrick]
|
395
406
|
end
|
396
407
|
|
397
408
|
def calc_delta(max_cardinality)
|
@@ -410,41 +421,40 @@ module GraphMatching
|
|
410
421
|
# > Compute delta2: the minimum slack on any edge between
|
411
422
|
# > an S-vertex and a free vertex.
|
412
423
|
# > (Van Rantwijk, mwmatching.py)
|
413
|
-
(0
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
end
|
421
|
-
end
|
424
|
+
(0...@nvertex).each do |v|
|
425
|
+
next unless @label[@in_blossom[v]] == LBL_FREE && !@best_edge[v].nil?
|
426
|
+
d = slack(@best_edge[v])
|
427
|
+
next unless delta_type == nil || d < delta
|
428
|
+
delta = d
|
429
|
+
delta_type = 2
|
430
|
+
delta_edge = @best_edge[v]
|
422
431
|
end
|
423
432
|
|
424
433
|
# > Compute delta3: half the minimum slack on any edge between
|
425
434
|
# > a pair of S-blossoms.
|
426
435
|
# > (Van Rantwijk, mwmatching.py)
|
427
|
-
(0
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
delta = d
|
433
|
-
delta_type = 3
|
434
|
-
delta_edge = @best_edge[b]
|
435
|
-
end
|
436
|
+
(0...2 * @nvertex).each do |b|
|
437
|
+
unless @blossom_parent[b].nil? &&
|
438
|
+
@label[b] == LBL_S &&
|
439
|
+
!@best_edge[b].nil?
|
440
|
+
next
|
436
441
|
end
|
442
|
+
kslack = slack(@best_edge[b])
|
443
|
+
d = kslack / 2 # Van Rantwijk had some type checking here. Why?
|
444
|
+
next unless delta_type.nil? || d < delta
|
445
|
+
delta = d
|
446
|
+
delta_type = 3
|
447
|
+
delta_edge = @best_edge[b]
|
437
448
|
end
|
438
449
|
|
439
450
|
# > Compute delta4: minimum z variable of any T-blossom.
|
440
451
|
# > (Van Rantwijk, mwmatching.py)
|
441
|
-
(@nvertex
|
452
|
+
(@nvertex...2 * @nvertex).each do |b|
|
442
453
|
top_t_blossom = top_level_blossom?(b) && @label[b] == LBL_T
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
end
|
454
|
+
next unless top_t_blossom && (delta_type.nil? || @dual[b] < delta)
|
455
|
+
delta = @dual[b]
|
456
|
+
delta_type = 4
|
457
|
+
delta_blossom = b
|
448
458
|
end
|
449
459
|
|
450
460
|
if delta_type.nil?
|
@@ -457,7 +467,7 @@ module GraphMatching
|
|
457
467
|
delta = [0, @dual[0, @nvertex].min].max
|
458
468
|
end
|
459
469
|
|
460
|
-
|
470
|
+
[delta, delta_type, delta_edge, delta_blossom]
|
461
471
|
end
|
462
472
|
|
463
473
|
# Returns nil if `k` is known to be an endpoint of a tight
|
@@ -609,16 +619,20 @@ module GraphMatching
|
|
609
619
|
|
610
620
|
# > Relabel the T-sub-blossom.
|
611
621
|
@label[@endpoint[p ^ 1]] = LBL_FREE
|
612
|
-
@label[
|
622
|
+
@label[
|
623
|
+
@endpoint[@blossom_endps[b][j - endptrick] ^ endptrick ^ 1]
|
624
|
+
] = LBL_FREE
|
613
625
|
assign_label(@endpoint[p ^ 1], LBL_T, p)
|
614
626
|
|
615
627
|
# > Step to the next S-sub-blossom and note its forward endpoint.
|
616
|
-
|
628
|
+
# Intentional floor division
|
629
|
+
@tight_edge[@blossom_endps[b][j - endptrick] / 2] = true
|
617
630
|
j += jstep
|
618
631
|
p = @blossom_endps[b][j - endptrick] ^ endptrick
|
619
632
|
|
620
633
|
# > Step to the next T-sub-blossom.
|
621
|
-
|
634
|
+
# Intentional floor division
|
635
|
+
@tight_edge[p / 2] = true
|
622
636
|
j += jstep
|
623
637
|
end
|
624
638
|
|
@@ -662,7 +676,7 @@ module GraphMatching
|
|
662
676
|
# > End of a stage; expand all S-blossoms which have dualvar = 0.
|
663
677
|
# > (Van Rantwijk, mwmatching.py)
|
664
678
|
def expand_tight_s_blossoms
|
665
|
-
(@nvertex
|
679
|
+
(@nvertex...2 * @nvertex).each do |b|
|
666
680
|
if top_level_blossom?(b) && @label[b] == LBL_S && @dual[b] == 0
|
667
681
|
expand_blossom(b, true)
|
668
682
|
end
|
@@ -686,11 +700,10 @@ module GraphMatching
|
|
686
700
|
# Data structures used throughout the algorithm.
|
687
701
|
def init_algorithm_structures
|
688
702
|
# > If v is a vertex,
|
689
|
-
# > mate[v] is the remote endpoint of its matched edge, or -1 if it is
|
690
|
-
# > (i.e. endpoint[mate[v]] is v's partner vertex).
|
703
|
+
# > mate[v] is the remote endpoint of its matched edge, or -1 if it is
|
704
|
+
# > single (i.e. endpoint[mate[v]] is v's partner vertex).
|
691
705
|
# > Initially all vertices are single; updated during augmentation.
|
692
706
|
# > (Van Rantwijk, mwmatching.py)
|
693
|
-
#
|
694
707
|
@mate = Array.new(@nvertex, nil)
|
695
708
|
|
696
709
|
# > If b is a top-level blossom,
|
@@ -699,37 +712,29 @@ module GraphMatching
|
|
699
712
|
# > 2 if b is a T-vertex/blossom.
|
700
713
|
# > The label of a vertex is found by looking at the label of its
|
701
714
|
# > top-level containing blossom.
|
702
|
-
# > If v is a vertex inside a T-blossom,
|
703
|
-
# >
|
704
|
-
# >
|
715
|
+
# > If v is a vertex inside a T-blossom, label[v] is 2 iff v is
|
716
|
+
# > reachable from an S-vertex outside the blossom. Labels are assigned
|
717
|
+
# > during a stage and reset after each augmentation.
|
705
718
|
# > (Van Rantwijk, mwmatching.py)
|
706
|
-
#
|
707
719
|
@label = rantwijk_array(LBL_FREE)
|
708
720
|
|
709
|
-
# > If b is a labeled top-level blossom,
|
710
|
-
# >
|
711
|
-
# >
|
712
|
-
# >
|
713
|
-
# >
|
714
|
-
# >
|
715
|
-
# > (Van Rantwijk, mwmatching.py)
|
716
|
-
#
|
721
|
+
# > If b is a labeled top-level blossom, labelend[b] is the remote
|
722
|
+
# > endpoint of the edge through which b obtained its label, or -1 if
|
723
|
+
# > b's base vertex is single. If v is a vertex inside a T-blossom and
|
724
|
+
# > label[v] == 2, labelend[v] is the remote endpoint of the edge
|
725
|
+
# > through which v is reachable from outside the blossom. (Van
|
726
|
+
# > Rantwijk, mwmatching.py)
|
717
727
|
@label_end = rantwijk_array(nil)
|
718
728
|
|
719
|
-
# > If v is a vertex,
|
720
|
-
# >
|
721
|
-
# >
|
722
|
-
# >
|
723
|
-
|
724
|
-
# > (Van Rantwijk, mwmatching.py)
|
725
|
-
#
|
726
|
-
@in_blossom = (0 ... @nvertex).to_a
|
729
|
+
# > If v is a vertex, inblossom[v] is the top-level blossom to which v
|
730
|
+
# > belongs. If v is a top-level vertex, v is itself a blossom (a
|
731
|
+
# > trivial blossom) and inblossom[v] == v. Initially all vertices are
|
732
|
+
# > top-level trivial blossoms. (Van Rantwijk, mwmatching.py)
|
733
|
+
@in_blossom = (0...@nvertex).to_a
|
727
734
|
|
728
|
-
# > If b is a sub-blossom,
|
729
|
-
# > blossomparent[b] is
|
730
|
-
# > If b is a top-level blossom, blossomparent[b] is -1.
|
735
|
+
# > If b is a sub-blossom, blossomparent[b] is its immediate parent
|
736
|
+
# > (sub-)blossom. If b is a top-level blossom, blossomparent[b] is -1.
|
731
737
|
# > (Van Rantwijk, mwmatching.py)
|
732
|
-
#
|
733
738
|
@blossom_parent = rantwijk_array(nil)
|
734
739
|
|
735
740
|
# A 2D array representing a tree of blossoms.
|
@@ -760,7 +765,7 @@ module GraphMatching
|
|
760
765
|
# > blossombase[b] is its base VERTEX (i.e. recursive sub-blossom).
|
761
766
|
# > (Van Rantwijk, mwmatching.py, line 153)
|
762
767
|
#
|
763
|
-
@blossom_base = (0
|
768
|
+
@blossom_base = (0...@nvertex).to_a + Array.new(@nvertex, nil)
|
764
769
|
|
765
770
|
# > If b is a non-trivial (sub-)blossom,
|
766
771
|
# > blossomendps[b] is a list of endpoints on its connecting edges,
|
@@ -792,7 +797,7 @@ module GraphMatching
|
|
792
797
|
|
793
798
|
# > List of currently unused blossom numbers.
|
794
799
|
# > (Van Rantwijk, mwmatching.py, line 174)
|
795
|
-
@unused_blossoms = (@nvertex
|
800
|
+
@unused_blossoms = (@nvertex...2 * @nvertex).to_a
|
796
801
|
|
797
802
|
# > If v is a vertex,
|
798
803
|
# > dualvar[v] = 2 * u(v) where u(v) is the v's variable in the dual
|
@@ -884,7 +889,7 @@ module GraphMatching
|
|
884
889
|
# > the queue. (Van Rantwijk, mwmatching.py, line 649)
|
885
890
|
def init_stage_labels
|
886
891
|
@label = rantwijk_array(LBL_FREE)
|
887
|
-
(0
|
892
|
+
(0...@nvertex).each do |v|
|
888
893
|
if @mate[v].nil? && @label[@in_blossom[v]] == LBL_FREE
|
889
894
|
assign_label(v, LBL_S)
|
890
895
|
end
|
@@ -1006,7 +1011,7 @@ module GraphMatching
|
|
1006
1011
|
augmented = false
|
1007
1012
|
|
1008
1013
|
@neighb_end[v].each do |p|
|
1009
|
-
k = p / 2 # floor division
|
1014
|
+
k = p / 2 # Intentional floor division
|
1010
1015
|
w = @endpoint[p]
|
1011
1016
|
|
1012
1017
|
if @in_blossom[v] == @in_blossom[w]
|
@@ -1050,7 +1055,7 @@ module GraphMatching
|
|
1050
1055
|
# > .. we make the following changes in the dual
|
1051
1056
|
# > variables. (Galil, 1986, p. 32)
|
1052
1057
|
def update_duals(delta)
|
1053
|
-
(0
|
1058
|
+
(0...@nvertex).each do |v|
|
1054
1059
|
case @label[@in_blossom[v]]
|
1055
1060
|
when LBL_S
|
1056
1061
|
@dual[v] -= delta
|
@@ -1060,16 +1065,15 @@ module GraphMatching
|
|
1060
1065
|
# No change to free vertexes
|
1061
1066
|
end
|
1062
1067
|
end
|
1063
|
-
(@nvertex
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
end
|
1068
|
+
(@nvertex...2 * @nvertex).each do |b|
|
1069
|
+
next unless top_level_blossom?(b)
|
1070
|
+
case @label[b]
|
1071
|
+
when LBL_S
|
1072
|
+
@dual[b] += delta
|
1073
|
+
when LBL_T
|
1074
|
+
@dual[b] -= delta
|
1075
|
+
else
|
1076
|
+
# No change to free blossoms
|
1073
1077
|
end
|
1074
1078
|
end
|
1075
1079
|
end
|