graph_matching 0.0.1

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 (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,147 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../graph/weighted_bigraph'
4
+ require_relative '../matching'
5
+ require_relative 'matching_algorithm'
6
+
7
+ module GraphMatching
8
+ module Algorithm
9
+
10
+ # `MWMBipartite` implements Maximum Weighted Matching in
11
+ # bipartite graphs. It extends Maximum Cardinality
12
+ # Matching for `Weighted` graphs.
13
+ class MWMBipartite < MCMBipartite
14
+
15
+ def initialize(graph)
16
+ assert(graph).is_a(Graph::WeightedBigraph)
17
+ super
18
+
19
+ # Optimization: Keeping a reference to the graph's weights
20
+ # in the instance, instead of calling `Weighted#w`
21
+ # thousands of times, is twice as fast.
22
+ @weight = graph.weight
23
+ end
24
+
25
+ def match
26
+ m = []
27
+ dogs, cats = g.partition
28
+ u = init_duals(cats, dogs)
29
+
30
+ # For each stage
31
+ while true do
32
+
33
+ # Clear all labels and marks
34
+ # Label all single dogs with S
35
+ aug_path = nil
36
+ predecessors = {}
37
+ t = Set.new
38
+ s = Set.new(dogs) - m
39
+ q = s.dup.to_a
40
+
41
+ # While searching
42
+ while aug_path.nil? && i = q.pop do
43
+
44
+ # Follow the unmatched edges (if any) to free (unlabeled)
45
+ # 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
65
+ end
66
+ end
67
+ end
68
+
69
+ # We have looked at every S-dog.
70
+ # If no `aug_path` was found, the search failed.
71
+ # Adjust the duals and search again.
72
+ if aug_path.nil?
73
+ d1 = calc_d1(s, u)
74
+ d2 = calc_d2(s, t, u)
75
+ d = [d1, d2].compact.min
76
+
77
+ # If d == d1, then the smallest dual is equal to the
78
+ # smallest slack, and the duals of all single dogs are
79
+ # zero. Therefore, we're totally done.
80
+ #
81
+ # Otherwise, adjust the duals by subtracting d from S-dog
82
+ # duals and adding d to T-cat duals.
83
+ if d == d1
84
+ break
85
+ else
86
+ s.each do |si| u[si] = u[si] - d end
87
+ t.each do |ti| u[ti] = u[ti] + d end
88
+ end
89
+
90
+ else
91
+ m = augment(m, aug_path)
92
+ end
93
+ end
94
+
95
+ Matching.gabow(m)
96
+ end
97
+
98
+ private
99
+
100
+ def assert_weighted_bipartite(graph)
101
+ unless weighted_bipartite?(graph)
102
+ raise ArgumentError, 'Expected a weighted bipartite graph'
103
+ end
104
+ end
105
+
106
+ # Returns d1, min of S-dog duals
107
+ def calc_d1(s, u)
108
+ u.values_at(*s).min
109
+ end
110
+
111
+ # Returns d2, the smallest slack between S-dogs and free
112
+ # cats. This is a fairly expensive method, due to the
113
+ # nested loop.
114
+ def calc_d2(s, t, u)
115
+ slacks = []
116
+ s.each do |s_dog|
117
+ g.each_adjacent(s_dog) do |cat|
118
+ unless t.include?(cat)
119
+ slacks.push π(u, s_dog, cat)
120
+ end
121
+ end
122
+ end
123
+ slacks.min
124
+ end
125
+
126
+ # Initialize the "dual" values
127
+ def init_duals(cats, dogs)
128
+ u = []
129
+ ui = g.max_w
130
+ dogs.each do |i| u[i] = ui end
131
+ cats.each do |j| u[j] = 0 end
132
+ u
133
+ end
134
+
135
+ def weighted_bipartite?(graph)
136
+ graph.respond_to?(:partition) && graph.respond_to?(:w)
137
+ end
138
+
139
+ # Returns the "slack" of an edge (Galil, 1986, p.30), the
140
+ # difference between an edge's duals and its weight.
141
+ def π(u, i, j)
142
+ u[i] + u[j] - @weight[i - 1][j - 1]
143
+ end
144
+
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,1086 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../graph/weighted_graph'
4
+ require_relative '../matching'
5
+ require_relative 'matching_algorithm'
6
+
7
+ module GraphMatching
8
+ module Algorithm
9
+
10
+ # `MWMGeneral` implements Maximum Weighted Matching in
11
+ # general graphs.
12
+ class MWMGeneral < MatchingAlgorithm
13
+
14
+ # If b is a top-level blossom,
15
+ # label[b] is 0 if b is unlabeled (free);
16
+ # 1 if b is an S-vertex/blossom;
17
+ # 2 if b is a T-vertex/blossom.
18
+ LBL_FREE = 0
19
+ LBL_S = 1
20
+ LBL_T = 2
21
+ LBL_CRUMB = 5
22
+ LBL_NAMES = ['Free', 'S', 'T', 'Crumb']
23
+
24
+ def initialize(graph)
25
+ assert(graph).is_a(Graph::WeightedGraph)
26
+ super
27
+ init_graph_structures
28
+ init_algorithm_structures
29
+ end
30
+
31
+ # > As in Problem 3, the algorithm consists of O(n) *stages*.
32
+ # > In each stage we look for an augmenting path using the
33
+ # > labeling R12 and the two cases C1, C2 as in the simple
34
+ # > algorithm for Problem 2, except that we only use edges
35
+ # > with π<sub>ij</sub> = 0. (Galil, 1986, p. 32)
36
+ #
37
+ # Van Rantwijk's implementation (and, consequently, this port)
38
+ # supports both maximum cardinality maximum weight matching
39
+ # and MWM irrespective of cardinality.
40
+ def match(max_cardinality)
41
+ return Matching.new if g.num_edges == 0
42
+
43
+ # Iterative *stages*. Each stage augments the matching.
44
+ # There can be at most n stages, where n is num. vertexes.
45
+ while true do
46
+ init_stage
47
+
48
+ # *sub-stages* either augment or scale the duals.
49
+ augmented = false
50
+ while true do
51
+
52
+ # > The search is conducted by scanning the S-vertices
53
+ # > in turn. (Galil, 1986, p. 26)
54
+ until augmented || @queue.empty?
55
+ augmented = scan_vertex(@queue.pop)
56
+ end
57
+
58
+ break if augmented
59
+
60
+ # > There is no augmenting path under these constraints;
61
+ # > compute delta and reduce slack in the optimization problem.
62
+ # > (Van Rantwijk, mwmatching.py, line 732)
63
+ delta, delta_type, delta_edge, delta_blossom = calc_delta(max_cardinality)
64
+ update_duals(delta)
65
+ optimum = act_on_minimum_delta(delta_type, delta_edge, delta_blossom)
66
+ break if optimum
67
+ end
68
+
69
+ # > Stop when no more augmenting path can be found.
70
+ # > (Van Rantwijk, mwmatching.py)
71
+ break unless augmented
72
+
73
+ expand_tight_s_blossoms
74
+ end
75
+
76
+ Matching.from_endpoints(@endpoint, @mate)
77
+ end
78
+
79
+ private
80
+
81
+ # > Take action at the point where minimum delta occurred.
82
+ # > (Van Rantwijk, mwmatching.py)
83
+ def act_on_minimum_delta(delta_type, delta_edge, delta_blossom)
84
+ optimum = false
85
+ case delta_type
86
+ when 1
87
+ # > No further improvement possible; optimum reached.
88
+ optimum = true
89
+ when 2
90
+ # > Use the least-slack edge to continue the search.
91
+ @tight_edge[delta_edge] = true
92
+ i, j = @edges[delta_edge].to_a
93
+ if @label[@in_blossom[i]] == LBL_FREE
94
+ i, j = j, i
95
+ end
96
+ assert_label(@in_blossom[i], LBL_S)
97
+ @queue.push(i)
98
+ when 3
99
+ # > Use the least-slack edge to continue the search.
100
+ @tight_edge[delta_edge] = true
101
+ i, j = @edges[delta_edge].to_a
102
+ assert_label(@in_blossom[i], LBL_S)
103
+ @queue.push(i)
104
+ when 4
105
+ # > Expand the least-z blossom.
106
+ expand_blossom(delta_blossom, false)
107
+ else
108
+ fail "Invalid delta_type: #{delta_type}"
109
+ end
110
+ optimum
111
+ end
112
+
113
+ # > Construct a new blossom with given base, containing edge
114
+ # > k which connects a pair of S vertices. Label the new
115
+ # > blossom as S; set its dual variable to zero; relabel its
116
+ # > T-vertices to S and add them to the queue.
117
+ # > (Van Rantwijk, mwmatching.py, line 270)
118
+ def add_blossom(base, k)
119
+ v, w = @edges[k].to_a
120
+ bb = @in_blossom[base]
121
+ bv = @in_blossom[v]
122
+ bw = @in_blossom[w]
123
+
124
+ # Create a new top-level blossom.
125
+ b = @unused_blossoms.pop
126
+ @blossom_base[b] = base
127
+ @blossom_parent[b] = nil
128
+ @blossom_parent[bb] = b
129
+
130
+ # > Make list of sub-blossoms and their interconnecting
131
+ # > edge endpoints.
132
+ # > (Van Rantwijk, mwmatching.py, line 284)
133
+ #
134
+ # 1. Clear the existing lists
135
+ # 2. Trace back from v to base
136
+ # 3. Reverse lists, add endpoint that connects the pair of S vertices
137
+ # 4. Trace back from w to base
138
+ #
139
+ @blossom_children[b] = []
140
+ @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]
145
+ end
146
+ @blossom_children[b] << bb
147
+ @blossom_children[b].reverse!
148
+ @blossom_endps[b].reverse!
149
+ @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)
154
+ end
155
+
156
+ # > Set label to S
157
+ assert(@label[bb]).eq(LBL_S)
158
+ @label[b] = LBL_S
159
+ @label_end[b] = @label_end[bb]
160
+
161
+ # > Set dual variable to zero.
162
+ @dual[b] = 0
163
+
164
+ # > Relabel vertices.
165
+ blossom_leaves(b).each do |v|
166
+ if @label[@in_blossom[v]] == LBL_T
167
+ # > This T-vertex now turns into an S-vertex because it
168
+ # > becomes part of an S-blossom; add it to the queue.
169
+ @queue << v
170
+ end
171
+ @in_blossom[v] = b
172
+ end
173
+
174
+ # > Compute blossombestedges[b].
175
+ best_edge_to = rantwijk_array(nil)
176
+ @blossom_children[b].each do |bv|
177
+ if @blossom_best_edges[bv].nil?
178
+ # > This subblossom [bv] does not have a list of least-
179
+ # > 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
183
+ }
184
+ }
185
+ else
186
+ # > Walk this subblossom's least-slack edges.
187
+ nblists = [@blossom_best_edges[bv]]
188
+ end
189
+
190
+ nblists.each do |nblist|
191
+ nblist.each do |k|
192
+ i, j = @edges[k].to_a
193
+ if @in_blossom[j] == b
194
+ i, j = j, i
195
+ end
196
+ 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
201
+ end
202
+ end
203
+ end
204
+
205
+ # > Forget about least-slack edges of the subblossom.
206
+ @blossom_best_edges[bv] = nil
207
+ @best_edge[bv] = nil
208
+ end
209
+
210
+ @blossom_best_edges[b] = best_edge_to.compact
211
+
212
+ # > Select bestedge[b]
213
+ @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
217
+ end
218
+ end
219
+ end
220
+
221
+ def assert_blossom_trace(b)
222
+ t = @label[b] == LBL_T
223
+ s = @label[b] == LBL_S
224
+ m = @label_end[b] == @mate[@blossom_base[b]]
225
+ unless t || s && m
226
+ fail <<-EOS
227
+ Assertion failed: Expected either:
228
+ 1. Current Bv to be a T-blossom, or
229
+ 2. Bv is an S-blossom and its base is matched to @label_end[bv]
230
+ EOS
231
+ end
232
+ end
233
+
234
+ def assert_label(ix, lbl)
235
+ unless @label[ix] == lbl
236
+ fail "Expected label at #{ix} to be #{LBL_NAMES[lbl]}"
237
+ end
238
+ end
239
+
240
+ # > Assign label t to the top-level blossom containing vertex w
241
+ # > and record the fact that w was reached through the edge with
242
+ # > remote endpoint p.
243
+ # > (Van Rantwijk, mwmatching.py)
244
+ #
245
+ def assign_label(w, t, p = nil)
246
+ b = @in_blossom[w]
247
+ assert_label(w, LBL_FREE)
248
+ assert_label(b, LBL_FREE)
249
+ @label[w] = @label[b] = t
250
+ @label_end[w] = @label_end[b] = p
251
+ @best_edge[w] = @best_edge[b] = nil
252
+ if t == LBL_S
253
+ # b became an S-vertex/blossom; add it(s vertices) to the queue.
254
+ @queue.concat(blossom_leaves(b))
255
+ elsif t == LBL_T
256
+ # b became a T-vertex/blossom; assign label S to its mate.
257
+ # (If b is a non-trivial blossom, its base is the only vertex
258
+ # with an external mate.)
259
+ base = @blossom_base[b]
260
+ if @mate[base].nil?
261
+ fail "Expected blossom #{b}'s base (#{base}) to be matched"
262
+ end
263
+
264
+ # Assign label S to the mate of blossom b's base.
265
+ # Remember, `mate` elements are pointers to "endpoints".
266
+ # The bitwise XOR is very clever. `mate[x]` and `mate[x] ^ 1`
267
+ # are connected "endpoints".
268
+ base_edge_endpoints = [@mate[base], @mate[base] ^ 1]
269
+ assign_label(@endpoint[base_edge_endpoints[0]], LBL_S, base_edge_endpoints[1])
270
+ else
271
+ fail ArgumentError, "Unexpected label: #{t}"
272
+ end
273
+ end
274
+
275
+ # > Swap matched/unmatched edges over an alternating path
276
+ # > through blossom b between vertex v and the base vertex.
277
+ # > Keep blossom bookkeeping consistent.
278
+ # > (Van Rantwijk, mwmatching.py, line 448)
279
+ def augment_blossom(b, v)
280
+ t = immediate_subblossom_of(b, v)
281
+
282
+ # > Recursively deal with the first sub-blossom.
283
+ if t >= @nvertex
284
+ augment_blossom(t, v)
285
+ end
286
+
287
+ # > Move along the blossom until we get to the base.
288
+ j, jstep, endptrick = blossom_loop_direction(b, t)
289
+ i = j
290
+ while j != 0
291
+ # > Step to the next sub-blossom and augment it recursively.
292
+ j += jstep
293
+ p = @blossom_endps[b][j - endptrick] ^ endptrick
294
+ x = @endpoint[p]
295
+ augment_blossom_step(b, j, x)
296
+
297
+ # > Step to the next sub-blossom and augment it recursively.
298
+ j += jstep
299
+ x = @endpoint[p ^ 1]
300
+ augment_blossom_step(b, j, x)
301
+
302
+ # > Match the edge connecting those sub-blossoms.
303
+ match_endpoint(p)
304
+ end
305
+
306
+ # > Rotate the list of sub-blossoms to put the new base at
307
+ # > the front.
308
+ @blossom_children[b].rotate!(i)
309
+ @blossom_endps[b].rotate!(i)
310
+ @blossom_base[b] = @blossom_base[@blossom_children[b][0]]
311
+ assert(@blossom_base[b]).eq(v)
312
+ end
313
+
314
+ def augment_blossom_step(b, j, x)
315
+ t = @blossom_children[b][j]
316
+ if t >= @nvertex
317
+ augment_blossom(t, x)
318
+ end
319
+ end
320
+
321
+ # > Swap matched/unmatched edges over an alternating path
322
+ # > between two single vertices. The augmenting path runs
323
+ # > through edge k, which connects a pair of S vertices.
324
+ # > (Van Rantwijk, mwmatching.py, line 494)
325
+ def augment_matching(k)
326
+ v, w = @edges[k].to_a
327
+ [[v, 2 * k + 1], [w, 2 * k]].each do |(s, p)|
328
+ # > Match vertex s to remote endpoint p. Then trace back from s
329
+ # > until we find a single vertex, swapping matched and unmatched
330
+ # > edges as we go.
331
+ # > (Van Rantwijk, mwmatching.py, line 504)
332
+ while true
333
+ bs = @in_blossom[s]
334
+ assert_label(bs, LBL_S)
335
+ assert(@label_end[bs]).eq(@mate[@blossom_base[bs]])
336
+ # > Augment through the S-blossom from s to base.
337
+ if bs >= @nvertex
338
+ augment_blossom(bs, s)
339
+ end
340
+ @mate[s] = p
341
+ # > Trace one step back.
342
+ # If we reach a single vertex, stop
343
+ break if @label_end[bs].nil?
344
+ t = @endpoint[@label_end[bs]]
345
+ bt = @in_blossom[t]
346
+ assert_label(bt, LBL_T)
347
+ # > Trace one step back.
348
+ assert(@label_end[bt]).not_nil
349
+ s = @endpoint[@label_end[bt]]
350
+ j = @endpoint[@label_end[bt] ^ 1]
351
+ # > Augment through the T-blossom from j to base.
352
+ assert(@blossom_base[bt]).eq(t)
353
+ if bt >= @nvertex
354
+ augment_blossom(bt, j)
355
+ end
356
+ @mate[j] = @label_end[bt]
357
+ # > Keep the opposite endpoint;
358
+ # > it will be assigned to mate[s] in the next step.
359
+ p = @label_end[bt] ^ 1
360
+ end
361
+ end
362
+ end
363
+
364
+ # TODO: Optimize by returning lazy iterator
365
+ def blossom_leaves(b, ary = [])
366
+ if b < @nvertex
367
+ ary.push(b)
368
+ else
369
+ @blossom_children[b].each do |c|
370
+ if c < @nvertex
371
+ ary.push(c)
372
+ else
373
+ ary.concat(blossom_leaves(c))
374
+ end
375
+ end
376
+ end
377
+ ary
378
+ end
379
+
380
+ # > Decide in which direction we will go round the blossom.
381
+ # > (Van Rantwijk, mwmatching.py, lines 385, 460)
382
+ def blossom_loop_direction(b, t)
383
+ j = @blossom_children[b].index(t)
384
+ if j.odd?
385
+ # > go forward and wrap
386
+ j -= @blossom_children[b].length
387
+ jstep = 1
388
+ endptrick = 0
389
+ else
390
+ # > go backward
391
+ jstep = -1
392
+ endptrick = 1
393
+ end
394
+ return j, jstep, endptrick
395
+ end
396
+
397
+ def calc_delta(max_cardinality)
398
+ delta = nil
399
+ delta_type = nil
400
+ delta_edge = nil
401
+ delta_blossom = nil
402
+
403
+ # > Compute delta1: the minumum value of any vertex dual.
404
+ # > (Van Rantwijk, mwmatching.py)
405
+ unless max_cardinality
406
+ delta_type = 1
407
+ delta = @dual[0, @nvertex].min
408
+ end
409
+
410
+ # > Compute delta2: the minimum slack on any edge between
411
+ # > an S-vertex and a free vertex.
412
+ # > (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
422
+ end
423
+
424
+ # > Compute delta3: half the minimum slack on any edge between
425
+ # > a pair of S-blossoms.
426
+ # > (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
+ end
437
+ end
438
+
439
+ # > Compute delta4: minimum z variable of any T-blossom.
440
+ # > (Van Rantwijk, mwmatching.py)
441
+ (@nvertex ... 2 * @nvertex).each do |b|
442
+ 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
448
+ end
449
+
450
+ if delta_type.nil?
451
+ # > No further improvement possible; max-cardinality optimum
452
+ # > reached. Do a final delta update to make the optimum
453
+ # > verifyable.
454
+ # > (Van Rantwijk, mwmatching.py)
455
+ assert(max_cardinality).eq(true)
456
+ delta_type = 1
457
+ delta = [0, @dual[0, @nvertex].min].max
458
+ end
459
+
460
+ return delta, delta_type, delta_edge, delta_blossom
461
+ end
462
+
463
+ # Returns nil if `k` is known to be an endpoint of a tight
464
+ # edge. Otherwise, calculates and returns the slack of `k`,
465
+ # and updates the `tight_edge` cache.
466
+ def calc_slack(k)
467
+ if @tight_edge[k]
468
+ nil
469
+ else
470
+ kslack = slack(k)
471
+ if kslack <= 0
472
+ @tight_edge[k] = true
473
+ end
474
+ kslack
475
+ end
476
+ end
477
+
478
+ # > w is a free vertex (or an unreached vertex inside
479
+ # > a T-blossom) but we can not reach it yet;
480
+ # > keep track of the least-slack edge that reaches w.
481
+ # > (Van Rantwijk, mwmatching.py, line 725)
482
+ def consider_loose_edge_to_free_vertex(w, k, kslack)
483
+ if @best_edge[w].nil? || kslack < slack(@best_edge[w])
484
+ @best_edge[w] = k
485
+ end
486
+ end
487
+
488
+ # While scanning neighbors of `v`, a loose edge to an
489
+ # S-blossom is found, and the `@best_edge` cache may
490
+ # be updated.
491
+ #
492
+ # > keep track of the least-slack non-allowable [loose] edge
493
+ # > to a different S-blossom.
494
+ # > (Van Rantwijk, mwmatching.py, line 717)
495
+ #
496
+ def consider_loose_edge_to_s_blossom(v, k, kslack)
497
+ b = @in_blossom[v]
498
+ if @best_edge[b].nil? || kslack < slack(@best_edge[b])
499
+ @best_edge[b] = k
500
+ end
501
+ end
502
+
503
+ # > If we scan the S-vertex *i* and consider the edge (i,j),
504
+ # > there are two cases:
505
+ # >
506
+ # > * (C1) j is free; or
507
+ # > * (C2) j is an S-vertex
508
+ # >
509
+ # > C2 cannot occur in the bipartite case. The case in
510
+ # > which j is a T-vertex is discarded.
511
+ # > (Galil, 1986, p. 26-27)
512
+ #
513
+ def consider_tight_edge(k, w, p, v)
514
+ augmented = false
515
+
516
+ if @label[@in_blossom[w]] == LBL_FREE
517
+
518
+ # > (C1) w is a free vertex;
519
+ # > label w with T and label its mate with S (R12).
520
+ # > (Van Rantwijk, mwmatching.py, line 690)
521
+ #
522
+ # > In case C1 we apply R12. (Galil, 1986, p. 27)
523
+ #
524
+ # > * (R1) If (i, j) is not matched and i is an S-person
525
+ # > and j a free (unlabeled) person then label j by T; and
526
+ # > * (R2) If (i, j) is matched and j is a T-person
527
+ # > and i a free person, then label i by S.
528
+ # >
529
+ # > Modified from (Galil, 1986, p. 25) as follows:
530
+ #
531
+ # > Any time R1 is used and j is labeled by T, R2 is
532
+ # > immediately used to label the spouse of j with S.
533
+ # > (Since j was not labeled before, it must be married
534
+ # > and its spouse must be unlabeled.) We call this
535
+ # > rule R12. (Galil, 1986, p. 26)
536
+ #
537
+ assign_label(w, LBL_T, p ^ 1)
538
+
539
+ elsif @label[@in_blossom[w]] == LBL_S
540
+
541
+ # > (C2) w is an S-vertex (not in the same blossom);
542
+ # > follow back-links to discover either an
543
+ # > augmenting path or a new blossom.
544
+ # > (Van Rantwijk, mwmatching.py, line 694)
545
+ #
546
+ base = scan_blossom(v, w)
547
+ if base.nil?
548
+ # > Found an augmenting path; augment the
549
+ # > matching and end this stage.
550
+ # > (Van Rantwijk, mwmatching.py, line 703)
551
+ augment_matching(k)
552
+ augmented = true
553
+ else
554
+ # > Found a new blossom; add it to the blossom
555
+ # > bookkeeping and turn it into an S-blossom.
556
+ # > (Van Rantwijk, mwmatching.py, line 699)
557
+ add_blossom(base, k)
558
+ end
559
+
560
+ elsif @label[w] == LBL_FREE
561
+
562
+ # > w is inside a T-blossom, but w itself has not
563
+ # > yet been reached from outside the blossom;
564
+ # > mark it as reached (we need this to relabel
565
+ # > during T-blossom expansion).
566
+ # > (Van Rantwijk, mwmatching.py, line 709)
567
+ #
568
+ assert_label(@in_blossom[w], LBL_T)
569
+ @label[w] = LBL_T
570
+ @label_end[w] = p ^ 1
571
+
572
+ end
573
+
574
+ augmented
575
+ end
576
+
577
+ # > Expand the given top-level blossom.
578
+ # > (Van Rantwijk, mwmatching.py, line 361)
579
+ #
580
+ # Blossoms are expanded during slack adjustment delta type 4,
581
+ # and after all stages are complete (endstage will be true).
582
+ #
583
+ def expand_blossom(b, endstage)
584
+ promote_sub_blossoms_of(b, endstage)
585
+
586
+ # > If we expand a T-blossom during a stage, its sub-blossoms
587
+ # > must be relabeled.
588
+ if !endstage && @label[b] == LBL_T
589
+ expand_t_blossom(b)
590
+ end
591
+
592
+ recycle_blossom_number(b)
593
+ end
594
+
595
+ # > Start at the sub-blossom through which the expanding
596
+ # > blossom obtained its label, and relabel sub-blossoms until
597
+ # > we reach the base.
598
+ # > Figure out through which sub-blossom the expanding blossom
599
+ # > obtained its label initially.
600
+ # > (Van Rantwijk, mwmatching.py, line 378)
601
+ def expand_t_blossom(b)
602
+ assert(@label_end[b]).not_nil
603
+ entry_child = @in_blossom[@endpoint[@label_end[b] ^ 1]]
604
+
605
+ # > Move along the blossom until we get to the base.
606
+ j, jstep, endptrick = blossom_loop_direction(b, entry_child)
607
+ p = @label_end[b]
608
+ while j != 0
609
+
610
+ # > Relabel the T-sub-blossom.
611
+ @label[@endpoint[p ^ 1]] = LBL_FREE
612
+ @label[@endpoint[@blossom_endps[b][j - endptrick] ^ endptrick ^ 1]] = LBL_FREE
613
+ assign_label(@endpoint[p ^ 1], LBL_T, p)
614
+
615
+ # > Step to the next S-sub-blossom and note its forward endpoint.
616
+ @tight_edge[@blossom_endps[b][j - endptrick] / 2] = true # floor division
617
+ j += jstep
618
+ p = @blossom_endps[b][j - endptrick] ^ endptrick
619
+
620
+ # > Step to the next T-sub-blossom.
621
+ @tight_edge[p / 2] = true # floor division
622
+ j += jstep
623
+ end
624
+
625
+ # > Relabel the base T-sub-blossom WITHOUT stepping through to
626
+ # > its mate (so don't call assignLabel).
627
+ bv = @blossom_children[b][j]
628
+ @label[@endpoint[p ^ 1]] = @label[bv] = LBL_T
629
+ @label_end[@endpoint[p ^ 1]] = @label_end[bv] = p
630
+ @best_edge[bv] = nil
631
+
632
+ # > Continue along the blossom until we get back to entrychild.
633
+ j += jstep
634
+ while @blossom_children[b][j] != entry_child
635
+
636
+ # > Examine the vertices of the sub-blossom to see whether
637
+ # > it is reachable from a neighbouring S-vertex outside the
638
+ # > expanding blossom.
639
+ bv = @blossom_children[b][j]
640
+ if @label[bv] == LBL_S
641
+ # > This sub-blossom just got label S through one of its
642
+ # > neighbours; leave it.
643
+ j += jstep
644
+ next
645
+ end
646
+
647
+ # > If the sub-blossom contains a reachable vertex, assign
648
+ # > label T to the sub-blossom.
649
+ v = first_labeled_blossom_leaf(bv)
650
+ unless v.nil?
651
+ assert_label(v, LBL_T)
652
+ assert(@in_blossom[v]).eq(bv)
653
+ @label[v] = LBL_FREE
654
+ @label[@endpoint[@mate[@blossom_base[bv]]]] = LBL_FREE
655
+ assign_label(v, LBL_T, @label_end[v])
656
+ end
657
+
658
+ j += jstep
659
+ end
660
+ end
661
+
662
+ # > End of a stage; expand all S-blossoms which have dualvar = 0.
663
+ # > (Van Rantwijk, mwmatching.py)
664
+ def expand_tight_s_blossoms
665
+ (@nvertex ... 2 * @nvertex).each do |b|
666
+ if top_level_blossom?(b) && @label[b] == LBL_S && @dual[b] == 0
667
+ expand_blossom(b, true)
668
+ end
669
+ end
670
+ end
671
+
672
+ def first_labeled_blossom_leaf(b)
673
+ blossom_leaves(b).find { |leaf| @label[leaf] != LBL_FREE }
674
+ end
675
+
676
+ # Starting from a vertex `v`, ascend the blossom tree, and
677
+ # return the sub-blossom immediately below `b`.
678
+ def immediate_subblossom_of(b, v)
679
+ t = v
680
+ while @blossom_parent[t] != b
681
+ t = @blossom_parent[t]
682
+ end
683
+ t
684
+ end
685
+
686
+ # Data structures used throughout the algorithm.
687
+ def init_algorithm_structures
688
+ # > 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).
691
+ # > Initially all vertices are single; updated during augmentation.
692
+ # > (Van Rantwijk, mwmatching.py)
693
+ #
694
+ @mate = Array.new(@nvertex, nil)
695
+
696
+ # > If b is a top-level blossom,
697
+ # > label[b] is 0 if b is unlabeled (free);
698
+ # > 1 if b is an S-vertex/blossom;
699
+ # > 2 if b is a T-vertex/blossom.
700
+ # > The label of a vertex is found by looking at the label of its
701
+ # > 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.
705
+ # > (Van Rantwijk, mwmatching.py)
706
+ #
707
+ @label = rantwijk_array(LBL_FREE)
708
+
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
+ #
717
+ @label_end = rantwijk_array(nil)
718
+
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
727
+
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.
731
+ # > (Van Rantwijk, mwmatching.py)
732
+ #
733
+ @blossom_parent = rantwijk_array(nil)
734
+
735
+ # A 2D array representing a tree of blossoms.
736
+ #
737
+ # > The blossom structure of a graph is represented by a
738
+ # > *blossom tree*. Its nodes are the graph G, the blossoms
739
+ # > of G, and all vertices included in blossoms. The root is
740
+ # > G, whose children are the maximal blossoms. .. Any
741
+ # > vertex is a leaf.
742
+ # > (Gabow, 1985, p. 91)
743
+ #
744
+ # Van Rantwijk implements the blossom tree with an array in
745
+ # two halves. The first half is "trivial" blossoms, vertexes,
746
+ # the leaves of the tree. The second half are non-trivial blossoms.
747
+ #
748
+ # > Vertices are numbered 0 .. (nvertex-1).
749
+ # > Non-trivial blossoms are numbered nvertex .. (2*nvertex-1)
750
+ # > (Van Rantwijk, mwmatching.py, line 58)
751
+ #
752
+ # > If b is a non-trivial (sub-)blossom, blossomchilds[b]
753
+ # > is an ordered list of its sub-blossoms, starting with
754
+ # > the base and going round the blossom.
755
+ # > (Van Rantwijk, mwmatching.py, line 144)
756
+ #
757
+ @blossom_children = rantwijk_array(nil)
758
+
759
+ # > If b is a (sub-)blossom,
760
+ # > blossombase[b] is its base VERTEX (i.e. recursive sub-blossom).
761
+ # > (Van Rantwijk, mwmatching.py, line 153)
762
+ #
763
+ @blossom_base = (0 ... @nvertex).to_a + Array.new(@nvertex, nil)
764
+
765
+ # > If b is a non-trivial (sub-)blossom,
766
+ # > blossomendps[b] is a list of endpoints on its connecting edges,
767
+ # > such that blossomendps[b][i] is the local endpoint of
768
+ # > blossomchilds[b][i] on the edge that connects it to
769
+ # > blossomchilds[b][wrap(i+1)].
770
+ # > (Van Rantwijk, mwmatching.py, line 147)
771
+ #
772
+ @blossom_endps = rantwijk_array(nil)
773
+
774
+ # > If v is a free vertex (or an unreached vertex inside a T-blossom),
775
+ # > bestedge[v] is the edge to an S-vertex with least slack,
776
+ # > or -1 if there is no such edge.
777
+ # > If b is a (possibly trivial) top-level S-blossom,
778
+ # > bestedge[b] is the least-slack edge to a different S-blossom,
779
+ # > or -1 if there is no such edge.
780
+ # > This is used for efficient computation of delta2 and delta3.
781
+ # > (Van Rantwijk, mwmatching.py)
782
+ #
783
+ @best_edge = rantwijk_array(nil)
784
+
785
+ # > If b is a non-trivial top-level S-blossom,
786
+ # > blossombestedges[b] is a list of least-slack edges to neighbouring
787
+ # > S-blossoms, or None if no such list has been computed yet.
788
+ # > This is used for efficient computation of delta3.
789
+ # > (Van Rantwijk, mwmatching.py, line 168)
790
+ #
791
+ @blossom_best_edges = rantwijk_array(nil)
792
+
793
+ # > List of currently unused blossom numbers.
794
+ # > (Van Rantwijk, mwmatching.py, line 174)
795
+ @unused_blossoms = (@nvertex ... 2 * @nvertex).to_a
796
+
797
+ # > If v is a vertex,
798
+ # > dualvar[v] = 2 * u(v) where u(v) is the v's variable in the dual
799
+ # > optimization problem (multiplication by two ensures integer values
800
+ # > throughout the algorithm if all edge weights are integers).
801
+ # > If b is a non-trivial blossom, dualvar[b] = z(b)
802
+ # > where z(b) is b's variable in the dual optimization
803
+ # > problem. (Van Rantwijk, mwmatching.py, line 177)
804
+ #
805
+ @dual = Array.new(@nvertex, g.max_w) + Array.new(@nvertex, 0)
806
+
807
+ # Optimization: Cache of tight (zero slack) edges. *Tight*
808
+ # is a term I attribute to Gabow, though it may be earlier.
809
+ #
810
+ # > Edge ij is *tight* if equality holds in [its dual
811
+ # > value function]. (Gabow, 1985, p. 91)
812
+ #
813
+ # Van Rantwijk calls this cache `allowedge`, denoting its use
814
+ # in the algorithm.
815
+ #
816
+ # > If allowedge[k] is true, edge k has zero slack in the optimization
817
+ # > problem; if allowedge[k] is false, the edge's slack may or may not
818
+ # > be zero.
819
+ @tight_edge = Array.new(@edges.length, false)
820
+
821
+ # Queue of newly discovered S-vertices.
822
+ @queue = []
823
+ end
824
+
825
+ # Builds data structures about the graph. These structures
826
+ # are not modified by the algorithm.
827
+ def init_graph_structures
828
+ @weight = g.weight
829
+
830
+ # The size of the array (or part of an array) used for
831
+ # vertexes (as opposed to blossoms) throughout this
832
+ # algorithm. It is *not*, as one might assume from the
833
+ # name, the number of vertexes in the graph.
834
+ @nvertex = g.max_v.to_i + 1
835
+
836
+ # Make a local copy of the edges. We'll refer to edges
837
+ # by number throughout throughout the algorithm and it's
838
+ # important that the order be consistent.
839
+ @edges = g.edges.map { |e| [e.source, e.target] }
840
+
841
+ # In Joris van Rantwijk's implementation, there seems to be
842
+ # a concept of "edge numbers". His `endpoint` array has two
843
+ # elements for each edge. His `mate` array "points to" his
844
+ # `endpoint` array. (See below) I'm sure there's a reason,
845
+ # but I don't understand yet.
846
+ #
847
+ # > If p is an edge endpoint, endpoint[p] is the vertex to
848
+ # > which endpoint p is attached. Not modified by the
849
+ # > algorithm. (Van Rantwijk, mwmatching.py, line 93)
850
+ @endpoint = @edges.flatten
851
+
852
+ # > If v is a vertex, neighbend[v] is the list of remote
853
+ # > endpoints of the edges attached to v. Not modified by
854
+ # > the algorithm. (Van Rantwijk, mwmatching.py, line 98)
855
+ @neighb_end = init_neighb_end(@nvertex, @edges)
856
+ end
857
+
858
+ def init_neighb_end(nvertex, edges)
859
+ neighb_end = Array.new(nvertex) { [] }
860
+ edges.each_with_index do |(i, j), k|
861
+ neighb_end[i].push(2 * k + 1)
862
+ neighb_end[j].push(2 * k)
863
+ end
864
+ neighb_end
865
+ end
866
+
867
+ def init_stage
868
+ init_stage_caches
869
+ @queue = []
870
+ init_stage_labels
871
+ end
872
+
873
+ # Clear the Van Rantwijk "best edge" caches
874
+ def init_stage_caches
875
+ @best_edge = rantwijk_array(nil)
876
+ @blossom_best_edges.fill(nil, @nvertex)
877
+ @tight_edge = Array.new(@edges.length, false)
878
+ end
879
+
880
+ # > We start by labeling all single persons S
881
+ # > (Galil, 1986, p. 26)
882
+ #
883
+ # > Label single blossoms/vertices with S and put them in
884
+ # > the queue. (Van Rantwijk, mwmatching.py, line 649)
885
+ def init_stage_labels
886
+ @label = rantwijk_array(LBL_FREE)
887
+ (0 ... @nvertex).each do |v|
888
+ if @mate[v].nil? && @label[@in_blossom[v]] == LBL_FREE
889
+ assign_label(v, LBL_S)
890
+ end
891
+ end
892
+ end
893
+
894
+ # Add endpoint p's edge to the matching.
895
+ def match_endpoint(p)
896
+ @mate[@endpoint[p]] = p ^ 1
897
+ @mate[@endpoint[p ^ 1]] = p
898
+ end
899
+
900
+ # > Convert sub-blossoms [of `b`] into top-level blossoms.
901
+ # > (Van Rantwijk, mwmatching.py, line 364)
902
+ def promote_sub_blossoms_of(b, endstage)
903
+ @blossom_children[b].each do |s|
904
+ @blossom_parent[s] = nil
905
+ if s < @nvertex
906
+ @in_blossom[s] = s
907
+ elsif endstage && @dual[s] == 0
908
+ expand_blossom(s, endstage)
909
+ else
910
+ blossom_leaves(s).each do |v|
911
+ @in_blossom[v] = s
912
+ end
913
+ end
914
+ end
915
+ end
916
+
917
+ def recycle_blossom_number(b)
918
+ @label[b] = nil
919
+ @label_end[b] = nil
920
+ @blossom_children[b] = nil
921
+ @blossom_endps[b] = nil
922
+ @blossom_base[b] = nil
923
+ @blossom_best_edges[b] = nil
924
+ @best_edge[b] = nil
925
+ @unused_blossoms.push(b)
926
+ end
927
+
928
+ # Backtrack to find an augmenting path (returns nil) or the
929
+ # base of a new blossom (returns base).
930
+ #
931
+ # > Backtrack from i and j, using the labels, to the
932
+ # > single persons s<sub>i</sub> and s<sub>j</sub>
933
+ # > from which i and j got their S labels. If
934
+ # > s<sub>i</sub> ≠ s<sub>j</sub>, we find an augmenting
935
+ # > path from s<sub>i</sub> to s<sub>j</sub> and augment
936
+ # > the matching. (Galil, 1986, p. 27)
937
+ #
938
+ # > Trace back from vertices v and w to discover either a new
939
+ # > blossom or an augmenting path. Return the base vertex of
940
+ # > the new blossom or -1. (Van Rantwijk, mwmatching.py, line 233)
941
+ def scan_blossom(v, w)
942
+ # > Trace back from v and w, placing breadcrumbs as we go.
943
+ path = []
944
+ base = nil
945
+ until v.nil? && w.nil?
946
+ # > Look for a breadcrumb in v's blossom or put a new breadcrumb.
947
+ b = @in_blossom[v]
948
+ if @label[b] & 4 != 0
949
+ base = @blossom_base[b]
950
+ break
951
+ end
952
+ assert_label(b, LBL_S)
953
+ path.push(b)
954
+ @label[b] = LBL_CRUMB
955
+ # > Trace one step back.
956
+ assert(@label_end[b]).eq(@mate[@blossom_base[b]])
957
+ if @label_end[b].nil?
958
+ # > The base of blossom b is single; stop tracing this path.
959
+ v = nil
960
+ else
961
+ v = @endpoint[@label_end[b]]
962
+ b = @in_blossom[v]
963
+ assert_label(b, LBL_T)
964
+ # > b is a T-blossom; trace one more step back.
965
+ assert(@label_end[b]).not_nil
966
+ v = @endpoint[@label_end[b]]
967
+ end
968
+
969
+ # > Swap v and w so that we alternate between both paths.
970
+ unless w.nil?
971
+ v, w = w, v
972
+ end
973
+ end
974
+ remove_breadcrumbs(path)
975
+ base
976
+ end
977
+
978
+ def remove_breadcrumbs(path)
979
+ path.each do |b| @label[b] = LBL_S end
980
+ end
981
+
982
+ # Trace a path around a blossom, from sub-blossom `bx` to
983
+ # blossom base `bb`, by following `@label_end`. At each
984
+ # step, `yield` the sub-blossom `bx`.
985
+ def trace_to_base(bx, bb)
986
+ while bx != bb
987
+ yield bx
988
+ assert_blossom_trace(bx)
989
+ assert(@label_end[bx]).not_nil
990
+ bx = @in_blossom[@endpoint[@label_end[bx]]]
991
+ end
992
+ end
993
+
994
+ # Returns an array of size 2n, where n is the number of
995
+ # vertexes. Common in Van Rantwijk's implementation, but
996
+ # the idea may come from Gabow (1985) or earlier.
997
+ def rantwijk_array(fill)
998
+ Array.new(2 * @nvertex, fill)
999
+ end
1000
+
1001
+ # > Scanning a vertex means considering in turn all its edges
1002
+ # > except the matched edge. (There will be at most one).
1003
+ # > (Galil, 1986, p. 26)
1004
+ def scan_vertex(v)
1005
+ assert_label(@in_blossom[v], LBL_S)
1006
+ augmented = false
1007
+
1008
+ @neighb_end[v].each do |p|
1009
+ k = p / 2 # floor division
1010
+ w = @endpoint[p]
1011
+
1012
+ if @in_blossom[v] == @in_blossom[w]
1013
+ # > this edge is internal to a blossom; ignore it
1014
+ # > (Van Rantwijk, mwmatching.py, line 681)
1015
+ next
1016
+ end
1017
+
1018
+ # Calculate slack of `k`'s edge and update tight_edge cache.
1019
+ kslack = calc_slack(k)
1020
+
1021
+ # > .. we only use edges with π<sub>ij</sub> = 0.
1022
+ # > (Galil, 1986, p. 32)
1023
+ if @tight_edge[k]
1024
+ augmented = consider_tight_edge(k, w, p, v)
1025
+ break if augmented
1026
+ elsif @label[@in_blossom[w]] == LBL_S
1027
+ consider_loose_edge_to_s_blossom(v, k, kslack)
1028
+ elsif @label[w] == LBL_FREE
1029
+ consider_loose_edge_to_free_vertex(w, k, kslack)
1030
+ end
1031
+ end
1032
+
1033
+ augmented
1034
+ end
1035
+
1036
+ # Van Rantwijk's implementation of slack does not match Galil's.
1037
+ #
1038
+ # > Return 2 * slack of edge k (does not work inside blossoms).
1039
+ # > (Van Rantwijk, mwmatching.py, line 194)
1040
+ #
1041
+ def slack(k)
1042
+ i, j = @edges[k]
1043
+ @dual[i] + @dual[j] - 2 * @weight[i - 1][j - 1]
1044
+ end
1045
+
1046
+ def top_level_blossom?(b)
1047
+ !@blossom_base[b].nil? && @blossom_parent[b].nil?
1048
+ end
1049
+
1050
+ # > .. we make the following changes in the dual
1051
+ # > variables. (Galil, 1986, p. 32)
1052
+ def update_duals(delta)
1053
+ (0 ... @nvertex).each do |v|
1054
+ case @label[@in_blossom[v]]
1055
+ when LBL_S
1056
+ @dual[v] -= delta
1057
+ when LBL_T
1058
+ @dual[v] += delta
1059
+ else
1060
+ # No change to free vertexes
1061
+ end
1062
+ 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
1073
+ end
1074
+ end
1075
+ end
1076
+
1077
+ # Uncomment to enable assertions. Slows down the algorithm
1078
+ # to O(n^4), but useful during development.
1079
+ #
1080
+ # require_relative 'mwmg_delta_assertions'
1081
+ # include MWMGDeltaAssertions
1082
+ # alias_method :calc_delta_without_assertions, :calc_delta
1083
+ # alias_method :calc_delta, :calc_delta_with_assertions
1084
+ end
1085
+ end
1086
+ end