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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +35 -0
  3. data/.rubocop_todo.yml +29 -256
  4. data/CHANGELOG.md +8 -2
  5. data/Rakefile +3 -3
  6. data/benchmark/mcm_bipartite/complete_bigraphs/benchmark.rb +1 -1
  7. data/benchmark/mcm_general/complete_graphs/benchmark.rb +1 -1
  8. data/benchmark/mwm_bipartite/complete_bigraphs/benchmark.rb +1 -1
  9. data/benchmark/mwm_bipartite/misc/calc_d2/benchmark.rb +1 -5
  10. data/benchmark/mwm_general/complete_graphs/benchmark.rb +2 -2
  11. data/benchmark/mwm_general/incomplete_graphs/benchmark.rb +6 -7
  12. data/graph_matching.gemspec +7 -11
  13. data/lib/graph_matching/algorithm/mcm_bipartite.rb +9 -8
  14. data/lib/graph_matching/algorithm/mcm_general.rb +2 -2
  15. data/lib/graph_matching/algorithm/mwm_bipartite.rb +25 -25
  16. data/lib/graph_matching/algorithm/mwm_general.rb +121 -117
  17. data/lib/graph_matching/algorithm/mwmg_delta_assertions.rb +47 -50
  18. data/lib/graph_matching/assertion.rb +4 -4
  19. data/lib/graph_matching/core_ext/set.rb +1 -1
  20. data/lib/graph_matching/graph/bigraph.rb +1 -1
  21. data/lib/graph_matching/graph/graph.rb +1 -1
  22. data/lib/graph_matching/graph/weighted.rb +11 -8
  23. data/lib/graph_matching/integer_vertexes.rb +2 -2
  24. data/lib/graph_matching/version.rb +1 -1
  25. data/lib/graph_matching/visualize.rb +5 -5
  26. data/profile/mwm_general/profile.rb +1 -1
  27. data/spec/graph_matching/algorithm/mcm_bipartite_spec.rb +1 -1
  28. data/spec/graph_matching/algorithm/mcm_general_spec.rb +1 -1
  29. data/spec/graph_matching/algorithm/mwm_bipartite_spec.rb +1 -1
  30. data/spec/graph_matching/algorithm/mwm_general_spec.rb +44 -41
  31. data/spec/graph_matching/graph/graph_spec.rb +4 -4
  32. data/spec/graph_matching/integer_vertexes_spec.rb +2 -2
  33. data/spec/spec_helper.rb +2 -2
  34. metadata +17 -17
@@ -28,6 +28,6 @@ MIN_SIZE.upto(MAX_SIZE) do |v|
28
28
  print "%5d\t" % [v]
29
29
  g = complete_bigraph(v)
30
30
  GC.disable
31
- puts Benchmark.realtime { g.maximum_cardinality_matching }
31
+ puts(Benchmark.realtime { g.maximum_cardinality_matching })
32
32
  GC.enable
33
33
  end
@@ -25,6 +25,6 @@ MIN_SIZE.upto(MAX_SIZE) do |v|
25
25
  print "%5d\t" % [v]
26
26
  g = complete_graph(v)
27
27
  GC.disable
28
- puts Benchmark.realtime { g.maximum_cardinality_matching }
28
+ puts(Benchmark.realtime { g.maximum_cardinality_matching })
29
29
  GC.enable
30
30
  end
@@ -38,6 +38,6 @@ MIN_SIZE.upto(MAX_SIZE) do |v|
38
38
  print "%5d\t" % [v]
39
39
  g = complete_weighted_bigraph(v)
40
40
  GC.disable
41
- puts Benchmark.realtime { g.maximum_weighted_matching }
41
+ puts(Benchmark.realtime { g.maximum_weighted_matching })
42
42
  GC.enable
43
43
  end
@@ -21,9 +21,5 @@ t = Set.new
21
21
  s = Set.new(dogs)
22
22
 
23
23
  GC.disable
24
- puts Benchmark.realtime {
25
- 100_000.times do
26
- a.send(:calc_d2, s, t, u)
27
- end
28
- }
24
+ puts(Benchmark.realtime { 100_000.times { a.send(:calc_d2, s, t, u) } })
29
25
  GC.enable
@@ -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 .. n - 1).reduce(:+)
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 Benchmark.realtime { g.maximum_weighted_matching(true) }
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 .. n - 1).reduce(:+).to_f * completeness).to_i + 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
- if rand < completeness
24
- g.add_edge(i, j)
25
- w = rand(max_weight)
26
- g.set_w([i, j], w)
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 Benchmark.realtime { g.maximum_weighted_matching(true) }
36
+ puts(Benchmark.realtime { g.maximum_weighted_matching(true) })
38
37
  GC.enable
39
38
  end
@@ -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-nav', '~> 0.2.4'
30
- spec.add_development_dependency 'rspec-core', '~> 3.2'
31
- spec.add_development_dependency 'rspec-expectations', '~> 3.2'
32
- spec.add_development_dependency 'rspec-mocks', '~> 3.2'
33
- spec.add_development_dependency 'rake', '~> 11.2'
34
- spec.add_development_dependency 'rubocop', '~> 0.42.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, v = g.partition
18
+ u = g.partition[0]
20
19
  m = []
21
20
 
22
- while true
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
- while aug_path.nil? && start = unmarked.pop
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 ,t).each do |vi|
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 "Invalid augmenting path"
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.has_key?(augmenting_path.last)
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
- while true do
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
- fail "Vertex #{v} has an unexpected label type"
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
- while true do
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
- while aug_path.nil? && i = q.pop do
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 ,t).each do |j|
47
- if π(u, i, j) == 0
48
- t << j
49
- predecessors[j] = i
50
-
51
- # If there are matched edges, follow each to a dog and
52
- # label the dog with S. Otherwise, backtrack to
53
- # construct an augmenting path.
54
- m_dogs = matched_adjacent(j, i, g, m)
55
-
56
- m_dogs.each do |md|
57
- s << md
58
- predecessors[md] = j
59
- end
60
-
61
- if m_dogs.empty?
62
- aug_path = backtrack_from(j, predecessors)
63
- break
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 π(u, s_dog, cat)
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 π(u, i, j)
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
- while true do
45
+ loop do
46
46
  init_stage
47
47
 
48
48
  # *sub-stages* either augment or scale the duals.
49
49
  augmented = false
50
- while true do
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, delta_type, delta_edge, delta_blossom = calc_delta(max_cardinality)
62
+ delta, d_type, d_edge, d_blossom = calc_delta(max_cardinality)
64
63
  update_duals(delta)
65
- optimum = act_on_minimum_delta(delta_type, delta_edge, delta_blossom)
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, j = j, 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, j = @edges[delta_edge].to_a
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
- fail "Invalid delta_type: #{delta_type}"
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 |bv|
142
- @blossom_parent[bv] = b
143
- @blossom_children[b] << bv
144
- @blossom_endps[b] << @label_end[bv]
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 |bw|
151
- @blossom_parent[bw] = b
152
- @blossom_children[b] << bw
153
- @blossom_endps[b] << (@label_end[bw] ^ 1)
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 |v|
166
- if @label[@in_blossom[v]] == LBL_T
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 << v
170
+ @queue << leaf
170
171
  end
171
- @in_blossom[v] = b
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 |bv|
177
- if @blossom_best_edges[bv].nil?
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(bv).map { |v|
181
- @neighb_end[v].map { |p|
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[bv]]
188
+ nblists = [@blossom_best_edges[child]]
188
189
  end
189
190
 
190
191
  nblists.each do |nblist|
191
- nblist.each do |k|
192
- i, j = @edges[k].to_a
192
+ nblist.each do |x|
193
+ i, j = @edges[x].to_a
193
194
  if @in_blossom[j] == b
194
- i, j = j, 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 != b &&
198
- @label[bj] == LBL_S &&
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[bv] = nil
207
- @best_edge[bv] = nil
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 |k|
215
- if @best_edge[b].nil? || slack(k) < slack(@best_edge[b])
216
- @best_edge[b] = k
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
- fail <<-EOS
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
- fail "Expected label at #{ix} to be #{LBL_NAMES[lbl]}"
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
- fail "Expected blossom #{b}'s base (#{base}) to be matched"
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(@endpoint[base_edge_endpoints[0]], LBL_S, base_edge_endpoints[1])
270
+ assign_label(
271
+ @endpoint[base_edge_endpoints[0]],
272
+ LBL_S,
273
+ base_edge_endpoints[1]
274
+ )
270
275
  else
271
- fail ArgumentError, "Unexpected label: #{t}"
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
- while true
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
- return j, jstep, endptrick
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 ... @nvertex).each do |v|
414
- if @label[@in_blossom[v]] == LBL_FREE && !@best_edge[v].nil?
415
- d = slack(@best_edge[v])
416
- if delta_type == nil || d < delta
417
- delta = d
418
- delta_type = 2
419
- delta_edge = @best_edge[v]
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 ... 2 * @nvertex).each do |b|
428
- if @blossom_parent[b].nil? && @label[b] == LBL_S && !@best_edge[b].nil?
429
- kslack = slack(@best_edge[b])
430
- d = kslack / 2 # Van Rantwijk had some type checking here. Why?
431
- if delta_type.nil? || d < delta
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 ... 2 * @nvertex).each do |b|
452
+ (@nvertex...2 * @nvertex).each do |b|
442
453
  top_t_blossom = top_level_blossom?(b) && @label[b] == LBL_T
443
- if top_t_blossom && (delta_type.nil? || @dual[b] < delta)
444
- delta = @dual[b]
445
- delta_type = 4
446
- delta_blossom = b
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
- return delta, delta_type, delta_edge, delta_blossom
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[@endpoint[@blossom_endps[b][j - endptrick] ^ endptrick ^ 1]] = LBL_FREE
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
- @tight_edge[@blossom_endps[b][j - endptrick] / 2] = true # floor division
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
- @tight_edge[p / 2] = true # floor division
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 ... 2 * @nvertex).each do |b|
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 single
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
- # > label[v] is 2 iff v is reachable from an S-vertex outside the blossom.
704
- # > Labels are assigned during a stage and reset after each augmentation.
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
- # > labelend[b] is the remote endpoint of the edge through which b obtained
711
- # > its label, or -1 if b's base vertex is single.
712
- # > If v is a vertex inside a T-blossom and label[v] == 2,
713
- # > labelend[v] is the remote endpoint of the edge through which v is
714
- # > reachable from outside the blossom.
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
- # > inblossom[v] is the top-level blossom to which v belongs.
721
- # > If v is a top-level vertex, v is itself a blossom (a trivial blossom)
722
- # > and inblossom[v] == v.
723
- # > Initially all vertices are top-level trivial blossoms.
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 its immediate parent (sub-)blossom.
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 ... @nvertex).to_a + Array.new(@nvertex, nil)
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 ... 2 * @nvertex).to_a
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 ... @nvertex).each do |v|
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 ... @nvertex).each do |v|
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 ... 2 * @nvertex).each do |b|
1064
- if top_level_blossom?(b)
1065
- case @label[b]
1066
- when LBL_S
1067
- @dual[b] += delta
1068
- when LBL_T
1069
- @dual[b] -= delta
1070
- else
1071
- # No change to free blossoms
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