graph_matching 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rubocop.yml +112 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +9 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +205 -0
  9. data/Rakefile +9 -0
  10. data/benchmark/mcm_bipartite/complete_bigraphs/benchmark.rb +33 -0
  11. data/benchmark/mcm_bipartite/complete_bigraphs/compare.gnuplot +19 -0
  12. data/benchmark/mcm_bipartite/complete_bigraphs/edges_times_vertexes.data +500 -0
  13. data/benchmark/mcm_bipartite/complete_bigraphs/plot.gnuplot +21 -0
  14. data/benchmark/mcm_bipartite/complete_bigraphs/plot.png +0 -0
  15. data/benchmark/mcm_bipartite/complete_bigraphs/time.data +499 -0
  16. data/benchmark/mcm_general/complete_graphs/benchmark.rb +30 -0
  17. data/benchmark/mcm_general/complete_graphs/plot.gnuplot +19 -0
  18. data/benchmark/mcm_general/complete_graphs/plot.png +0 -0
  19. data/benchmark/mcm_general/complete_graphs/time.data +499 -0
  20. data/benchmark/mcm_general/complete_graphs/v_cubed.data +500 -0
  21. data/benchmark/mwm_bipartite/complete_bigraphs/benchmark.rb +43 -0
  22. data/benchmark/mwm_bipartite/complete_bigraphs/nmN.data +499 -0
  23. data/benchmark/mwm_bipartite/complete_bigraphs/nmN.xlsx +0 -0
  24. data/benchmark/mwm_bipartite/complete_bigraphs/plot.gnuplot +22 -0
  25. data/benchmark/mwm_bipartite/complete_bigraphs/plot.png +0 -0
  26. data/benchmark/mwm_bipartite/complete_bigraphs/time.data +299 -0
  27. data/benchmark/mwm_bipartite/misc/calc_d2/benchmark.rb +29 -0
  28. data/benchmark/mwm_general/complete_graphs/benchmark.rb +32 -0
  29. data/benchmark/mwm_general/complete_graphs/compare.gnuplot +19 -0
  30. data/benchmark/mwm_general/complete_graphs/mn_log_n.data +299 -0
  31. data/benchmark/mwm_general/complete_graphs/mn_log_n.xlsx +0 -0
  32. data/benchmark/mwm_general/complete_graphs/plot.gnuplot +22 -0
  33. data/benchmark/mwm_general/complete_graphs/plot.png +0 -0
  34. data/benchmark/mwm_general/complete_graphs/time.data +299 -0
  35. data/benchmark/mwm_general/incomplete_graphs/benchmark.rb +39 -0
  36. data/benchmark/mwm_general/incomplete_graphs/plot.gnuplot +22 -0
  37. data/benchmark/mwm_general/incomplete_graphs/plot.png +0 -0
  38. data/benchmark/mwm_general/incomplete_graphs/time_10_pct.data +299 -0
  39. data/benchmark/mwm_general/incomplete_graphs/time_20_pct.data +299 -0
  40. data/benchmark/mwm_general/incomplete_graphs/time_30_pct.data +299 -0
  41. data/graph_matching.gemspec +35 -0
  42. data/lib/graph_matching.rb +15 -0
  43. data/lib/graph_matching/algorithm/matching_algorithm.rb +23 -0
  44. data/lib/graph_matching/algorithm/mcm_bipartite.rb +118 -0
  45. data/lib/graph_matching/algorithm/mcm_general.rb +289 -0
  46. data/lib/graph_matching/algorithm/mwm_bipartite.rb +147 -0
  47. data/lib/graph_matching/algorithm/mwm_general.rb +1086 -0
  48. data/lib/graph_matching/algorithm/mwmg_delta_assertions.rb +94 -0
  49. data/lib/graph_matching/assertion.rb +41 -0
  50. data/lib/graph_matching/core_ext/set.rb +36 -0
  51. data/lib/graph_matching/directed_edge_set.rb +31 -0
  52. data/lib/graph_matching/errors.rb +23 -0
  53. data/lib/graph_matching/graph/bigraph.rb +37 -0
  54. data/lib/graph_matching/graph/graph.rb +63 -0
  55. data/lib/graph_matching/graph/weighted.rb +112 -0
  56. data/lib/graph_matching/graph/weighted_bigraph.rb +17 -0
  57. data/lib/graph_matching/graph/weighted_graph.rb +17 -0
  58. data/lib/graph_matching/integer_vertexes.rb +29 -0
  59. data/lib/graph_matching/matching.rb +120 -0
  60. data/lib/graph_matching/ordered_set.rb +59 -0
  61. data/lib/graph_matching/version.rb +6 -0
  62. data/lib/graph_matching/visualize.rb +93 -0
  63. data/profile/mcm_bipartite/compare.sh +15 -0
  64. data/profile/mcm_bipartite/publish.sh +12 -0
  65. data/profile/mwm_general/compare.sh +15 -0
  66. data/profile/mwm_general/profile.rb +28 -0
  67. data/profile/mwm_general/publish.sh +12 -0
  68. data/research/1965_edmonds.pdf +0 -0
  69. data/research/1975_even_kariv.pdf +0 -0
  70. data/research/1976_gabow.pdf +0 -0
  71. data/research/1980_micali_vazirani.pdf +0 -0
  72. data/research/1985_gabow.pdf +0 -0
  73. data/research/2002_tarjan.pdf +0 -0
  74. data/research/2013_zwick.pdf +0 -0
  75. data/research/examples/unweighted_general/1.txt +86 -0
  76. data/research/goodwin.pdf +0 -0
  77. data/research/kavathekar-scribe.pdf +0 -0
  78. data/research/kusner.pdf +0 -0
  79. data/research/van_rantwijk/mwm_example.py +19 -0
  80. data/research/van_rantwijk/mwmatching.py +945 -0
  81. data/spec/graph_matching/algorithm/matching_algorithm_spec.rb +14 -0
  82. data/spec/graph_matching/algorithm/mcm_bipartite_spec.rb +98 -0
  83. data/spec/graph_matching/algorithm/mcm_general_spec.rb +159 -0
  84. data/spec/graph_matching/algorithm/mwm_bipartite_spec.rb +82 -0
  85. data/spec/graph_matching/algorithm/mwm_general_spec.rb +439 -0
  86. data/spec/graph_matching/graph/bigraph_spec.rb +73 -0
  87. data/spec/graph_matching/graph/graph_spec.rb +53 -0
  88. data/spec/graph_matching/graph/weighted_spec.rb +29 -0
  89. data/spec/graph_matching/integer_vertexes_spec.rb +21 -0
  90. data/spec/graph_matching/matching_spec.rb +89 -0
  91. data/spec/graph_matching/visualize_spec.rb +38 -0
  92. data/spec/graph_matching_spec.rb +9 -0
  93. data/spec/spec_helper.rb +26 -0
  94. metadata +263 -0
@@ -0,0 +1,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