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
Binary file
Binary file
@@ -0,0 +1,19 @@
1
+ from mwmatching import maxWeightMatching
2
+ from random import randrange
3
+ from time import clock
4
+ from math import pow
5
+
6
+ for n in list(range(2, 151)):
7
+ n_edges = (pow(n, 2) + n) / 2
8
+ edges = []
9
+ for i in list(range(n - 1)):
10
+ for j in list(range(i + 1, n)):
11
+ edges.append([i, j, randrange(0, n_edges)])
12
+
13
+ # Return a list "mate", such that mate[i] == j if vertex i is matched to
14
+ # vertex j, and mate[i] == -1 if vertex i is not matched.
15
+ before = clock()
16
+ match = maxWeightMatching(edges, maxcardinality=True)
17
+ after = clock()
18
+
19
+ print str(n) + "\t" + str(after - before)
@@ -0,0 +1,945 @@
1
+ """Weighted maximum matching in general graphs.
2
+
3
+ The algorithm is taken from "Efficient Algorithms for Finding Maximum
4
+ Matching in Graphs" by Zvi Galil, ACM Computing Surveys, 1986.
5
+ It is based on the "blossom" method for finding augmenting paths and
6
+ the "primal-dual" method for finding a matching of maximum weight, both
7
+ due to Jack Edmonds.
8
+ Some ideas came from "Implementation of algorithms for maximum matching
9
+ on non-bipartite graphs" by H.J. Gabow, Standford Ph.D. thesis, 1973.
10
+
11
+ A C program for maximum weight matching by Ed Rothberg was used extensively
12
+ to validate this new code.
13
+ """
14
+
15
+ #
16
+ # Changes:
17
+ #
18
+ # 2013-04-07
19
+ # * Added Python 3 compatibility with contributions from Daniel Saunders.
20
+ #
21
+ # 2008-06-08
22
+ # * First release.
23
+ #
24
+
25
+ from __future__ import print_function
26
+
27
+ # If assigned, DEBUG(str) is called with lots of debug messages.
28
+ DEBUG = None
29
+ """def DEBUG(s):
30
+ from sys import stderr
31
+ print('DEBUG:', s, file=stderr)
32
+ """
33
+
34
+ # Check delta2/delta3 computation after every substage;
35
+ # only works on integer weights, slows down the algorithm to O(n^4).
36
+ CHECK_DELTA = False
37
+
38
+ # Check optimality of solution before returning; only works on integer weights.
39
+ CHECK_OPTIMUM = True
40
+
41
+
42
+ def maxWeightMatching(edges, maxcardinality=False):
43
+ """Compute a maximum-weighted matching in the general undirected
44
+ weighted graph given by "edges". If "maxcardinality" is true,
45
+ only maximum-cardinality matchings are considered as solutions.
46
+
47
+ Edges is a sequence of tuples (i, j, wt) describing an undirected
48
+ edge between vertex i and vertex j with weight wt. There is at most
49
+ one edge between any two vertices; no vertex has an edge to itself.
50
+ Vertices are identified by consecutive, non-negative integers.
51
+
52
+ Return a list "mate", such that mate[i] == j if vertex i is
53
+ matched to vertex j, and mate[i] == -1 if vertex i is not matched.
54
+
55
+ This function takes time O(n ** 3)."""
56
+
57
+ #
58
+ # Vertices are numbered 0 .. (nvertex-1).
59
+ # Non-trivial blossoms are numbered nvertex .. (2*nvertex-1)
60
+ #
61
+ # Edges are numbered 0 .. (nedge-1).
62
+ # Edge endpoints are numbered 0 .. (2*nedge-1), such that endpoints
63
+ # (2*k) and (2*k+1) both belong to edge k.
64
+ #
65
+ # Many terms used in the comments (sub-blossom, T-vertex) come from
66
+ # the paper by Galil; read the paper before reading this code.
67
+ #
68
+
69
+ # Python 2/3 compatibility.
70
+ from sys import version as sys_version
71
+ if sys_version < '3':
72
+ integer_types = (int, long)
73
+ else:
74
+ integer_types = (int,)
75
+
76
+ # Deal swiftly with empty graphs.
77
+ if not edges:
78
+ return [ ]
79
+
80
+ # Count vertices.
81
+ nedge = len(edges)
82
+ nvertex = 0
83
+ for (i, j, w) in edges:
84
+ assert i >= 0 and j >= 0 and i != j
85
+ if i >= nvertex:
86
+ nvertex = i + 1
87
+ if j >= nvertex:
88
+ nvertex = j + 1
89
+
90
+ # Find the maximum edge weight.
91
+ maxweight = max(0, max([ wt for (i, j, wt) in edges ]))
92
+
93
+ # If p is an edge endpoint,
94
+ # endpoint[p] is the vertex to which endpoint p is attached.
95
+ # Not modified by the algorithm.
96
+ endpoint = [ edges[p//2][p%2] for p in range(2*nedge) ]
97
+
98
+ # If v is a vertex,
99
+ # neighbend[v] is the list of remote endpoints of the edges attached to v.
100
+ # Not modified by the algorithm.
101
+ neighbend = [ [ ] for i in range(nvertex) ]
102
+ for k in range(len(edges)):
103
+ (i, j, w) = edges[k]
104
+ neighbend[i].append(2*k+1)
105
+ neighbend[j].append(2*k)
106
+
107
+ # If v is a vertex,
108
+ # mate[v] is the remote endpoint of its matched edge, or -1 if it is single
109
+ # (i.e. endpoint[mate[v]] is v's partner vertex).
110
+ # Initially all vertices are single; updated during augmentation.
111
+ mate = nvertex * [ -1 ]
112
+
113
+ # If b is a top-level blossom,
114
+ # label[b] is 0 if b is unlabeled (free);
115
+ # 1 if b is an S-vertex/blossom;
116
+ # 2 if b is a T-vertex/blossom.
117
+ # The label of a vertex is found by looking at the label of its
118
+ # top-level containing blossom.
119
+ # If v is a vertex inside a T-blossom,
120
+ # label[v] is 2 iff v is reachable from an S-vertex outside the blossom.
121
+ # Labels are assigned during a stage and reset after each augmentation.
122
+ label = (2 * nvertex) * [ 0 ]
123
+
124
+ # If b is a labeled top-level blossom,
125
+ # labelend[b] is the remote endpoint of the edge through which b obtained
126
+ # its label, or -1 if b's base vertex is single.
127
+ # If v is a vertex inside a T-blossom and label[v] == 2,
128
+ # labelend[v] is the remote endpoint of the edge through which v is
129
+ # reachable from outside the blossom.
130
+ labelend = (2 * nvertex) * [ -1 ]
131
+
132
+ # If v is a vertex,
133
+ # inblossom[v] is the top-level blossom to which v belongs.
134
+ # If v is a top-level vertex, v is itself a blossom (a trivial blossom)
135
+ # and inblossom[v] == v.
136
+ # Initially all vertices are top-level trivial blossoms.
137
+ inblossom = list(range(nvertex))
138
+
139
+ # If b is a sub-blossom,
140
+ # blossomparent[b] is its immediate parent (sub-)blossom.
141
+ # If b is a top-level blossom, blossomparent[b] is -1.
142
+ blossomparent = (2 * nvertex) * [ -1 ]
143
+
144
+ # If b is a non-trivial (sub-)blossom,
145
+ # blossomchilds[b] is an ordered list of its sub-blossoms, starting with
146
+ # the base and going round the blossom.
147
+ blossomchilds = (2 * nvertex) * [ None ]
148
+
149
+ # If b is a (sub-)blossom,
150
+ # blossombase[b] is its base VERTEX (i.e. recursive sub-blossom).
151
+ blossombase = list(range(nvertex)) + nvertex * [ -1 ]
152
+
153
+ # If b is a non-trivial (sub-)blossom,
154
+ # blossomendps[b] is a list of endpoints on its connecting edges,
155
+ # such that blossomendps[b][i] is the local endpoint of blossomchilds[b][i]
156
+ # on the edge that connects it to blossomchilds[b][wrap(i+1)].
157
+ blossomendps = (2 * nvertex) * [ None ]
158
+
159
+ # If v is a free vertex (or an unreached vertex inside a T-blossom),
160
+ # bestedge[v] is the edge to an S-vertex with least slack,
161
+ # or -1 if there is no such edge.
162
+ # If b is a (possibly trivial) top-level S-blossom,
163
+ # bestedge[b] is the least-slack edge to a different S-blossom,
164
+ # or -1 if there is no such edge.
165
+ # This is used for efficient computation of delta2 and delta3.
166
+ bestedge = (2 * nvertex) * [ -1 ]
167
+
168
+ # If b is a non-trivial top-level S-blossom,
169
+ # blossombestedges[b] is a list of least-slack edges to neighbouring
170
+ # S-blossoms, or None if no such list has been computed yet.
171
+ # This is used for efficient computation of delta3.
172
+ blossombestedges = (2 * nvertex) * [ None ]
173
+
174
+ # List of currently unused blossom numbers.
175
+ unusedblossoms = list(range(nvertex, 2*nvertex))
176
+
177
+ # If v is a vertex,
178
+ # dualvar[v] = 2 * u(v) where u(v) is the v's variable in the dual
179
+ # optimization problem (multiplication by two ensures integer values
180
+ # throughout the algorithm if all edge weights are integers).
181
+ # If b is a non-trivial blossom,
182
+ # dualvar[b] = z(b) where z(b) is b's variable in the dual optimization
183
+ # problem.
184
+ dualvar = nvertex * [ maxweight ] + nvertex * [ 0 ]
185
+
186
+ # If allowedge[k] is true, edge k has zero slack in the optimization
187
+ # problem; if allowedge[k] is false, the edge's slack may or may not
188
+ # be zero.
189
+ allowedge = nedge * [ False ]
190
+
191
+ # Queue of newly discovered S-vertices.
192
+ queue = [ ]
193
+
194
+ # Return 2 * slack of edge k (does not work inside blossoms).
195
+ def slack(k):
196
+ (i, j, wt) = edges[k]
197
+ return dualvar[i] + dualvar[j] - 2 * wt
198
+
199
+ # Generate the leaf vertices of a blossom.
200
+ def blossomLeaves(b):
201
+ if b < nvertex:
202
+ yield b
203
+ else:
204
+ for t in blossomchilds[b]:
205
+ if t < nvertex:
206
+ yield t
207
+ else:
208
+ for v in blossomLeaves(t):
209
+ yield v
210
+
211
+ # Assign label t to the top-level blossom containing vertex w
212
+ # and record the fact that w was reached through the edge with
213
+ # remote endpoint p.
214
+ def assignLabel(w, t, p):
215
+ if DEBUG: DEBUG('assignLabel(%d,%d,%d)' % (w, t, p))
216
+ b = inblossom[w]
217
+ assert label[w] == 0 and label[b] == 0
218
+ label[w] = label[b] = t
219
+ labelend[w] = labelend[b] = p
220
+ bestedge[w] = bestedge[b] = -1
221
+ if t == 1:
222
+ # b became an S-vertex/blossom; add it(s vertices) to the queue.
223
+ queue.extend(blossomLeaves(b))
224
+ if DEBUG: DEBUG('PUSH ' + str(list(blossomLeaves(b))))
225
+ elif t == 2:
226
+ # b became a T-vertex/blossom; assign label S to its mate.
227
+ # (If b is a non-trivial blossom, its base is the only vertex
228
+ # with an external mate.)
229
+ base = blossombase[b]
230
+ assert mate[base] >= 0
231
+ assignLabel(endpoint[mate[base]], 1, mate[base] ^ 1)
232
+
233
+ # Trace back from vertices v and w to discover either a new blossom
234
+ # or an augmenting path. Return the base vertex of the new blossom or -1.
235
+ def scanBlossom(v, w):
236
+ if DEBUG: DEBUG('scanBlossom(%d,%d)' % (v, w))
237
+ # Trace back from v and w, placing breadcrumbs as we go.
238
+ path = [ ]
239
+ base = -1
240
+ while v != -1 or w != -1:
241
+ # Look for a breadcrumb in v's blossom or put a new breadcrumb.
242
+ b = inblossom[v]
243
+ if label[b] & 4:
244
+ base = blossombase[b]
245
+ break
246
+ assert label[b] == 1
247
+ path.append(b)
248
+ label[b] = 5
249
+ # Trace one step back.
250
+ assert labelend[b] == mate[blossombase[b]]
251
+ if labelend[b] == -1:
252
+ # The base of blossom b is single; stop tracing this path.
253
+ v = -1
254
+ else:
255
+ v = endpoint[labelend[b]]
256
+ b = inblossom[v]
257
+ assert label[b] == 2
258
+ # b is a T-blossom; trace one more step back.
259
+ assert labelend[b] >= 0
260
+ v = endpoint[labelend[b]]
261
+ # Swap v and w so that we alternate between both paths.
262
+ if w != -1:
263
+ v, w = w, v
264
+ # Remove breadcrumbs.
265
+ for b in path:
266
+ label[b] = 1
267
+ # Return base vertex, if we found one.
268
+ return base
269
+
270
+ # Construct a new blossom with given base, containing edge k which
271
+ # connects a pair of S vertices. Label the new blossom as S; set its dual
272
+ # variable to zero; relabel its T-vertices to S and add them to the queue.
273
+ def addBlossom(base, k):
274
+ (v, w, wt) = edges[k]
275
+ bb = inblossom[base]
276
+ bv = inblossom[v]
277
+ bw = inblossom[w]
278
+ # Create blossom.
279
+ b = unusedblossoms.pop()
280
+ if DEBUG: DEBUG('addBlossom(%d,%d) (v=%d w=%d) -> %d' % (base, k, v, w, b))
281
+ blossombase[b] = base
282
+ blossomparent[b] = -1
283
+ blossomparent[bb] = b
284
+ # Make list of sub-blossoms and their interconnecting edge endpoints.
285
+ blossomchilds[b] = path = [ ]
286
+ blossomendps[b] = endps = [ ]
287
+ # Trace back from v to base.
288
+ while bv != bb:
289
+ # Add bv to the new blossom.
290
+ blossomparent[bv] = b
291
+ path.append(bv)
292
+ endps.append(labelend[bv])
293
+ assert (label[bv] == 2 or
294
+ (label[bv] == 1 and labelend[bv] == mate[blossombase[bv]]))
295
+ # Trace one step back.
296
+ assert labelend[bv] >= 0
297
+ v = endpoint[labelend[bv]]
298
+ bv = inblossom[v]
299
+ # Reverse lists, add endpoint that connects the pair of S vertices.
300
+ path.append(bb)
301
+ path.reverse()
302
+ endps.reverse()
303
+ endps.append(2*k)
304
+ # Trace back from w to base.
305
+ while bw != bb:
306
+ # Add bw to the new blossom.
307
+ blossomparent[bw] = b
308
+ path.append(bw)
309
+ endps.append(labelend[bw] ^ 1)
310
+ assert (label[bw] == 2 or
311
+ (label[bw] == 1 and labelend[bw] == mate[blossombase[bw]]))
312
+ # Trace one step back.
313
+ assert labelend[bw] >= 0
314
+ w = endpoint[labelend[bw]]
315
+ bw = inblossom[w]
316
+ # Set label to S.
317
+ assert label[bb] == 1
318
+ label[b] = 1
319
+ labelend[b] = labelend[bb]
320
+ # Set dual variable to zero.
321
+ dualvar[b] = 0
322
+ # Relabel vertices.
323
+ for v in blossomLeaves(b):
324
+ if label[inblossom[v]] == 2:
325
+ # This T-vertex now turns into an S-vertex because it becomes
326
+ # part of an S-blossom; add it to the queue.
327
+ queue.append(v)
328
+ inblossom[v] = b
329
+ # Compute blossombestedges[b].
330
+ bestedgeto = (2 * nvertex) * [ -1 ]
331
+ for bv in path:
332
+ if blossombestedges[bv] is None:
333
+ # This subblossom does not have a list of least-slack edges;
334
+ # get the information from the vertices.
335
+ nblists = [ [ p // 2 for p in neighbend[v] ]
336
+ for v in blossomLeaves(bv) ]
337
+ else:
338
+ # Walk this subblossom's least-slack edges.
339
+ nblists = [ blossombestedges[bv] ]
340
+ for nblist in nblists:
341
+ for k in nblist:
342
+ (i, j, wt) = edges[k]
343
+ if inblossom[j] == b:
344
+ i, j = j, i
345
+ bj = inblossom[j]
346
+ if (bj != b and label[bj] == 1 and
347
+ (bestedgeto[bj] == -1 or
348
+ slack(k) < slack(bestedgeto[bj]))):
349
+ bestedgeto[bj] = k
350
+ # Forget about least-slack edges of the subblossom.
351
+ blossombestedges[bv] = None
352
+ bestedge[bv] = -1
353
+ blossombestedges[b] = [ k for k in bestedgeto if k != -1 ]
354
+ # Select bestedge[b].
355
+ bestedge[b] = -1
356
+ for k in blossombestedges[b]:
357
+ if bestedge[b] == -1 or slack(k) < slack(bestedge[b]):
358
+ bestedge[b] = k
359
+ if DEBUG: DEBUG('blossomchilds[%d]=' % b + repr(blossomchilds[b]))
360
+
361
+ # Expand the given top-level blossom.
362
+ def expandBlossom(b, endstage):
363
+ if DEBUG: DEBUG('expandBlossom(%d,%d) %s' % (b, endstage, repr(blossomchilds[b])))
364
+ # Convert sub-blossoms into top-level blossoms.
365
+ for s in blossomchilds[b]:
366
+ blossomparent[s] = -1
367
+ if s < nvertex:
368
+ inblossom[s] = s
369
+ elif endstage and dualvar[s] == 0:
370
+ # Recursively expand this sub-blossom.
371
+ expandBlossom(s, endstage)
372
+ else:
373
+ for v in blossomLeaves(s):
374
+ inblossom[v] = s
375
+ # If we expand a T-blossom during a stage, its sub-blossoms must be
376
+ # relabeled.
377
+ if (not endstage) and label[b] == 2:
378
+ # Start at the sub-blossom through which the expanding
379
+ # blossom obtained its label, and relabel sub-blossoms untili
380
+ # we reach the base.
381
+ # Figure out through which sub-blossom the expanding blossom
382
+ # obtained its label initially.
383
+ assert labelend[b] >= 0
384
+ entrychild = inblossom[endpoint[labelend[b] ^ 1]]
385
+ # Decide in which direction we will go round the blossom.
386
+ j = blossomchilds[b].index(entrychild)
387
+ if j & 1:
388
+ # Start index is odd; go forward and wrap.
389
+ j -= len(blossomchilds[b])
390
+ jstep = 1
391
+ endptrick = 0
392
+ else:
393
+ # Start index is even; go backward.
394
+ jstep = -1
395
+ endptrick = 1
396
+ # Move along the blossom until we get to the base.
397
+ p = labelend[b]
398
+ while j != 0:
399
+ # Relabel the T-sub-blossom.
400
+ label[endpoint[p ^ 1]] = 0
401
+ label[endpoint[blossomendps[b][j-endptrick]^endptrick^1]] = 0
402
+ assignLabel(endpoint[p ^ 1], 2, p)
403
+ # Step to the next S-sub-blossom and note its forward endpoint.
404
+ allowedge[blossomendps[b][j-endptrick]//2] = True
405
+ j += jstep
406
+ p = blossomendps[b][j-endptrick] ^ endptrick
407
+ # Step to the next T-sub-blossom.
408
+ allowedge[p//2] = True
409
+ j += jstep
410
+ # Relabel the base T-sub-blossom WITHOUT stepping through to
411
+ # its mate (so don't call assignLabel).
412
+ bv = blossomchilds[b][j]
413
+ label[endpoint[p ^ 1]] = label[bv] = 2
414
+ labelend[endpoint[p ^ 1]] = labelend[bv] = p
415
+ bestedge[bv] = -1
416
+ # Continue along the blossom until we get back to entrychild.
417
+ j += jstep
418
+ while blossomchilds[b][j] != entrychild:
419
+ # Examine the vertices of the sub-blossom to see whether
420
+ # it is reachable from a neighbouring S-vertex outside the
421
+ # expanding blossom.
422
+ bv = blossomchilds[b][j]
423
+ if label[bv] == 1:
424
+ # This sub-blossom just got label S through one of its
425
+ # neighbours; leave it.
426
+ j += jstep
427
+ continue
428
+ for v in blossomLeaves(bv):
429
+ if label[v] != 0:
430
+ break
431
+ # If the sub-blossom contains a reachable vertex, assign
432
+ # label T to the sub-blossom.
433
+ if label[v] != 0:
434
+ assert label[v] == 2
435
+ assert inblossom[v] == bv
436
+ label[v] = 0
437
+ label[endpoint[mate[blossombase[bv]]]] = 0
438
+ assignLabel(v, 2, labelend[v])
439
+ j += jstep
440
+ # Recycle the blossom number.
441
+ label[b] = labelend[b] = -1
442
+ blossomchilds[b] = blossomendps[b] = None
443
+ blossombase[b] = -1
444
+ blossombestedges[b] = None
445
+ bestedge[b] = -1
446
+ unusedblossoms.append(b)
447
+
448
+ # Swap matched/unmatched edges over an alternating path through blossom b
449
+ # between vertex v and the base vertex. Keep blossom bookkeeping consistent.
450
+ def augmentBlossom(b, v):
451
+ if DEBUG: DEBUG('augmentBlossom(%d,%d)' % (b, v))
452
+ # Bubble up through the blossom tree from vertex v to an immediate
453
+ # sub-blossom of b.
454
+ t = v
455
+ while blossomparent[t] != b:
456
+ t = blossomparent[t]
457
+ # Recursively deal with the first sub-blossom.
458
+ if t >= nvertex:
459
+ augmentBlossom(t, v)
460
+ # Decide in which direction we will go round the blossom.
461
+ i = j = blossomchilds[b].index(t)
462
+ if i & 1:
463
+ # Start index is odd; go forward and wrap.
464
+ j -= len(blossomchilds[b])
465
+ jstep = 1
466
+ endptrick = 0
467
+ else:
468
+ # Start index is even; go backward.
469
+ jstep = -1
470
+ endptrick = 1
471
+ # Move along the blossom until we get to the base.
472
+ while j != 0:
473
+ # Step to the next sub-blossom and augment it recursively.
474
+ j += jstep
475
+ t = blossomchilds[b][j]
476
+ p = blossomendps[b][j-endptrick] ^ endptrick
477
+ if t >= nvertex:
478
+ augmentBlossom(t, endpoint[p])
479
+ # Step to the next sub-blossom and augment it recursively.
480
+ j += jstep
481
+ t = blossomchilds[b][j]
482
+ if t >= nvertex:
483
+ augmentBlossom(t, endpoint[p ^ 1])
484
+ # Match the edge connecting those sub-blossoms.
485
+ mate[endpoint[p]] = p ^ 1
486
+ mate[endpoint[p ^ 1]] = p
487
+ if DEBUG: DEBUG('PAIR %d %d (k=%d)' % (endpoint[p], endpoint[p^1], p//2))
488
+ # Rotate the list of sub-blossoms to put the new base at the front.
489
+ blossomchilds[b] = blossomchilds[b][i:] + blossomchilds[b][:i]
490
+ blossomendps[b] = blossomendps[b][i:] + blossomendps[b][:i]
491
+ blossombase[b] = blossombase[blossomchilds[b][0]]
492
+ assert blossombase[b] == v
493
+
494
+ # Swap matched/unmatched edges over an alternating path between two
495
+ # single vertices. The augmenting path runs through edge k, which
496
+ # connects a pair of S vertices.
497
+ def augmentMatching(k):
498
+ (v, w, wt) = edges[k]
499
+ if DEBUG: DEBUG('augmentMatching(%d) (v=%d w=%d)' % (k, v, w))
500
+ if DEBUG: DEBUG('PAIR %d %d (k=%d)' % (v, w, k))
501
+ for (s, p) in ((v, 2*k+1), (w, 2*k)):
502
+ # Match vertex s to remote endpoint p. Then trace back from s
503
+ # until we find a single vertex, swapping matched and unmatched
504
+ # edges as we go.
505
+ while 1:
506
+ bs = inblossom[s]
507
+ assert label[bs] == 1
508
+ assert labelend[bs] == mate[blossombase[bs]]
509
+ # Augment through the S-blossom from s to base.
510
+ if bs >= nvertex:
511
+ augmentBlossom(bs, s)
512
+ # Update mate[s]
513
+ mate[s] = p
514
+ # Trace one step back.
515
+ if labelend[bs] == -1:
516
+ # Reached single vertex; stop.
517
+ break
518
+ t = endpoint[labelend[bs]]
519
+ bt = inblossom[t]
520
+ assert label[bt] == 2
521
+ # Trace one step back.
522
+ assert labelend[bt] >= 0
523
+ s = endpoint[labelend[bt]]
524
+ j = endpoint[labelend[bt] ^ 1]
525
+ # Augment through the T-blossom from j to base.
526
+ assert blossombase[bt] == t
527
+ if bt >= nvertex:
528
+ augmentBlossom(bt, j)
529
+ # Update mate[j]
530
+ mate[j] = labelend[bt]
531
+ # Keep the opposite endpoint;
532
+ # it will be assigned to mate[s] in the next step.
533
+ p = labelend[bt] ^ 1
534
+ if DEBUG: DEBUG('PAIR %d %d (k=%d)' % (s, t, p//2))
535
+
536
+ # Verify that the optimum solution has been reached.
537
+ def verifyOptimum():
538
+ if maxcardinality:
539
+ # Vertices may have negative dual;
540
+ # find a constant non-negative number to add to all vertex duals.
541
+ vdualoffset = max(0, -min(dualvar[:nvertex]))
542
+ else:
543
+ vdualoffset = 0
544
+ # 0. all dual variables are non-negative
545
+ assert min(dualvar[:nvertex]) + vdualoffset >= 0
546
+ assert min(dualvar[nvertex:]) >= 0
547
+ # 0. all edges have non-negative slack and
548
+ # 1. all matched edges have zero slack;
549
+ for k in range(nedge):
550
+ (i, j, wt) = edges[k]
551
+ s = dualvar[i] + dualvar[j] - 2 * wt
552
+ iblossoms = [ i ]
553
+ jblossoms = [ j ]
554
+ while blossomparent[iblossoms[-1]] != -1:
555
+ iblossoms.append(blossomparent[iblossoms[-1]])
556
+ while blossomparent[jblossoms[-1]] != -1:
557
+ jblossoms.append(blossomparent[jblossoms[-1]])
558
+ iblossoms.reverse()
559
+ jblossoms.reverse()
560
+ for (bi, bj) in zip(iblossoms, jblossoms):
561
+ if bi != bj:
562
+ break
563
+ s += 2 * dualvar[bi]
564
+ assert s >= 0
565
+ if mate[i] // 2 == k or mate[j] // 2 == k:
566
+ assert mate[i] // 2 == k and mate[j] // 2 == k
567
+ assert s == 0
568
+ # 2. all single vertices have zero dual value;
569
+ for v in range(nvertex):
570
+ assert mate[v] >= 0 or dualvar[v] + vdualoffset == 0
571
+ # 3. all blossoms with positive dual value are full.
572
+ for b in range(nvertex, 2*nvertex):
573
+ if blossombase[b] >= 0 and dualvar[b] > 0:
574
+ assert len(blossomendps[b]) % 2 == 1
575
+ for p in blossomendps[b][1::2]:
576
+ assert mate[endpoint[p]] == p ^ 1
577
+ assert mate[endpoint[p ^ 1]] == p
578
+ # Ok.
579
+
580
+ # Check optimized delta2 against a trivial computation.
581
+ def checkDelta2():
582
+ for v in range(nvertex):
583
+ if label[inblossom[v]] == 0:
584
+ bd = None
585
+ bk = -1
586
+ for p in neighbend[v]:
587
+ k = p // 2
588
+ w = endpoint[p]
589
+ if label[inblossom[w]] == 1:
590
+ d = slack(k)
591
+ if bk == -1 or d < bd:
592
+ bk = k
593
+ bd = d
594
+ if DEBUG and (bestedge[v] != -1 or bk != -1) and (bestedge[v] == -1 or bd != slack(bestedge[v])):
595
+ DEBUG('v=' + str(v) + ' bk=' + str(bk) + ' bd=' + str(bd) + ' bestedge=' + str(bestedge[v]) + ' slack=' + str(slack(bestedge[v])))
596
+ assert (bk == -1 and bestedge[v] == -1) or (bestedge[v] != -1 and bd == slack(bestedge[v]))
597
+
598
+ # Check optimized delta3 against a trivial computation.
599
+ def checkDelta3():
600
+ bk = -1
601
+ bd = None
602
+ tbk = -1
603
+ tbd = None
604
+ for b in range(2 * nvertex):
605
+ if blossomparent[b] == -1 and label[b] == 1:
606
+ for v in blossomLeaves(b):
607
+ for p in neighbend[v]:
608
+ k = p // 2
609
+ w = endpoint[p]
610
+ if inblossom[w] != b and label[inblossom[w]] == 1:
611
+ d = slack(k)
612
+ if bk == -1 or d < bd:
613
+ bk = k
614
+ bd = d
615
+ if bestedge[b] != -1:
616
+ (i, j, wt) = edges[bestedge[b]]
617
+ assert inblossom[i] == b or inblossom[j] == b
618
+ assert inblossom[i] != b or inblossom[j] != b
619
+ assert label[inblossom[i]] == 1 and label[inblossom[j]] == 1
620
+ if tbk == -1 or slack(bestedge[b]) < tbd:
621
+ tbk = bestedge[b]
622
+ tbd = slack(bestedge[b])
623
+ if DEBUG and bd != tbd:
624
+ DEBUG('bk=%d tbk=%d bd=%s tbd=%s' % (bk, tbk, repr(bd), repr(tbd)))
625
+ assert bd == tbd
626
+
627
+ # Main loop: continue until no further improvement is possible.
628
+ for t in range(nvertex):
629
+
630
+ # Each iteration of this loop is a "stage".
631
+ # A stage finds an augmenting path and uses that to improve
632
+ # the matching.
633
+ if DEBUG: DEBUG('STAGE %d' % t)
634
+
635
+ # Remove labels from top-level blossoms/vertices.
636
+ label[:] = (2 * nvertex) * [ 0 ]
637
+
638
+ # Forget all about least-slack edges.
639
+ bestedge[:] = (2 * nvertex) * [ -1 ]
640
+ blossombestedges[nvertex:] = nvertex * [ None ]
641
+
642
+ # Loss of labeling means that we can not be sure that currently
643
+ # allowable edges remain allowable througout this stage.
644
+ allowedge[:] = nedge * [ False ]
645
+
646
+ # Make queue empty.
647
+ queue[:] = [ ]
648
+
649
+ # Label single blossoms/vertices with S and put them in the queue.
650
+ for v in range(nvertex):
651
+ if mate[v] == -1 and label[inblossom[v]] == 0:
652
+ assignLabel(v, 1, -1)
653
+
654
+ # Loop until we succeed in augmenting the matching.
655
+ augmented = 0
656
+ while 1:
657
+
658
+ # Each iteration of this loop is a "substage".
659
+ # A substage tries to find an augmenting path;
660
+ # if found, the path is used to improve the matching and
661
+ # the stage ends. If there is no augmenting path, the
662
+ # primal-dual method is used to pump some slack out of
663
+ # the dual variables.
664
+ if DEBUG: DEBUG('SUBSTAGE')
665
+
666
+ # Continue labeling until all vertices which are reachable
667
+ # through an alternating path have got a label.
668
+ while queue and not augmented:
669
+
670
+ # Take an S vertex from the queue.
671
+ v = queue.pop()
672
+ if DEBUG: DEBUG('POP v=%d' % v)
673
+ assert label[inblossom[v]] == 1
674
+
675
+ # Scan its neighbours:
676
+ for p in neighbend[v]:
677
+ k = p // 2
678
+ w = endpoint[p]
679
+ # w is a neighbour to v
680
+ if inblossom[v] == inblossom[w]:
681
+ # this edge is internal to a blossom; ignore it
682
+ continue
683
+ if not allowedge[k]:
684
+ kslack = slack(k)
685
+ if kslack <= 0:
686
+ # edge k has zero slack => it is allowable
687
+ allowedge[k] = True
688
+ if allowedge[k]:
689
+ if label[inblossom[w]] == 0:
690
+ # (C1) w is a free vertex;
691
+ # label w with T and label its mate with S (R12).
692
+ assignLabel(w, 2, p ^ 1)
693
+ elif label[inblossom[w]] == 1:
694
+ # (C2) w is an S-vertex (not in the same blossom);
695
+ # follow back-links to discover either an
696
+ # augmenting path or a new blossom.
697
+ base = scanBlossom(v, w)
698
+ if base >= 0:
699
+ # Found a new blossom; add it to the blossom
700
+ # bookkeeping and turn it into an S-blossom.
701
+ addBlossom(base, k)
702
+ else:
703
+ # Found an augmenting path; augment the
704
+ # matching and end this stage.
705
+ augmentMatching(k)
706
+ augmented = 1
707
+ break
708
+ elif label[w] == 0:
709
+ # w is inside a T-blossom, but w itself has not
710
+ # yet been reached from outside the blossom;
711
+ # mark it as reached (we need this to relabel
712
+ # during T-blossom expansion).
713
+ assert label[inblossom[w]] == 2
714
+ label[w] = 2
715
+ labelend[w] = p ^ 1
716
+ elif label[inblossom[w]] == 1:
717
+ # keep track of the least-slack non-allowable edge to
718
+ # a different S-blossom.
719
+ b = inblossom[v]
720
+ if bestedge[b] == -1 or kslack < slack(bestedge[b]):
721
+ bestedge[b] = k
722
+ elif label[w] == 0:
723
+ # w is a free vertex (or an unreached vertex inside
724
+ # a T-blossom) but we can not reach it yet;
725
+ # keep track of the least-slack edge that reaches w.
726
+ if bestedge[w] == -1 or kslack < slack(bestedge[w]):
727
+ bestedge[w] = k
728
+
729
+ if augmented:
730
+ break
731
+
732
+ # There is no augmenting path under these constraints;
733
+ # compute delta and reduce slack in the optimization problem.
734
+ # (Note that our vertex dual variables, edge slacks and delta's
735
+ # are pre-multiplied by two.)
736
+ deltatype = -1
737
+ delta = deltaedge = deltablossom = None
738
+
739
+ # Verify data structures for delta2/delta3 computation.
740
+ if CHECK_DELTA:
741
+ checkDelta2()
742
+ checkDelta3()
743
+
744
+ # Compute delta1: the minumum value of any vertex dual.
745
+ if not maxcardinality:
746
+ deltatype = 1
747
+ delta = min(dualvar[:nvertex])
748
+
749
+ # Compute delta2: the minimum slack on any edge between
750
+ # an S-vertex and a free vertex.
751
+ for v in range(nvertex):
752
+ if label[inblossom[v]] == 0 and bestedge[v] != -1:
753
+ d = slack(bestedge[v])
754
+ if deltatype == -1 or d < delta:
755
+ delta = d
756
+ deltatype = 2
757
+ deltaedge = bestedge[v]
758
+
759
+ # Compute delta3: half the minimum slack on any edge between
760
+ # a pair of S-blossoms.
761
+ for b in range(2 * nvertex):
762
+ if ( blossomparent[b] == -1 and label[b] == 1 and
763
+ bestedge[b] != -1 ):
764
+ kslack = slack(bestedge[b])
765
+ if isinstance(kslack, integer_types):
766
+ assert (kslack % 2) == 0
767
+ d = kslack // 2
768
+ else:
769
+ d = kslack / 2
770
+ if deltatype == -1 or d < delta:
771
+ delta = d
772
+ deltatype = 3
773
+ deltaedge = bestedge[b]
774
+
775
+ # Compute delta4: minimum z variable of any T-blossom.
776
+ for b in range(nvertex, 2*nvertex):
777
+ if ( blossombase[b] >= 0 and blossomparent[b] == -1 and
778
+ label[b] == 2 and
779
+ (deltatype == -1 or dualvar[b] < delta) ):
780
+ delta = dualvar[b]
781
+ deltatype = 4
782
+ deltablossom = b
783
+
784
+ if deltatype == -1:
785
+ # No further improvement possible; max-cardinality optimum
786
+ # reached. Do a final delta update to make the optimum
787
+ # verifyable.
788
+ assert maxcardinality
789
+ deltatype = 1
790
+ delta = max(0, min(dualvar[:nvertex]))
791
+
792
+ # Update dual variables according to delta.
793
+ for v in range(nvertex):
794
+ if label[inblossom[v]] == 1:
795
+ # S-vertex: 2*u = 2*u - 2*delta
796
+ dualvar[v] -= delta
797
+ elif label[inblossom[v]] == 2:
798
+ # T-vertex: 2*u = 2*u + 2*delta
799
+ dualvar[v] += delta
800
+ for b in range(nvertex, 2*nvertex):
801
+ if blossombase[b] >= 0 and blossomparent[b] == -1:
802
+ if label[b] == 1:
803
+ # top-level S-blossom: z = z + 2*delta
804
+ dualvar[b] += delta
805
+ elif label[b] == 2:
806
+ # top-level T-blossom: z = z - 2*delta
807
+ dualvar[b] -= delta
808
+
809
+ # Take action at the point where minimum delta occurred.
810
+ if DEBUG: DEBUG('delta%d=%f' % (deltatype, delta))
811
+ if deltatype == 1:
812
+ # No further improvement possible; optimum reached.
813
+ break
814
+ elif deltatype == 2:
815
+ # Use the least-slack edge to continue the search.
816
+ allowedge[deltaedge] = True
817
+ (i, j, wt) = edges[deltaedge]
818
+ if label[inblossom[i]] == 0:
819
+ i, j = j, i
820
+ assert label[inblossom[i]] == 1
821
+ queue.append(i)
822
+ elif deltatype == 3:
823
+ # Use the least-slack edge to continue the search.
824
+ allowedge[deltaedge] = True
825
+ (i, j, wt) = edges[deltaedge]
826
+ assert label[inblossom[i]] == 1
827
+ queue.append(i)
828
+ elif deltatype == 4:
829
+ # Expand the least-z blossom.
830
+ expandBlossom(deltablossom, False)
831
+
832
+ # End of a this substage.
833
+
834
+ # Stop when no more augmenting path can be found.
835
+ if not augmented:
836
+ break
837
+
838
+ # End of a stage; expand all S-blossoms which have dualvar = 0.
839
+ for b in range(nvertex, 2*nvertex):
840
+ if ( blossomparent[b] == -1 and blossombase[b] >= 0 and
841
+ label[b] == 1 and dualvar[b] == 0 ):
842
+ expandBlossom(b, True)
843
+
844
+ # Verify that we reached the optimum solution.
845
+ if CHECK_OPTIMUM:
846
+ verifyOptimum()
847
+
848
+ # Transform mate[] such that mate[v] is the vertex to which v is paired.
849
+ for v in range(nvertex):
850
+ if mate[v] >= 0:
851
+ mate[v] = endpoint[mate[v]]
852
+ for v in range(nvertex):
853
+ assert mate[v] == -1 or mate[mate[v]] == v
854
+
855
+ return mate
856
+
857
+
858
+ # Unit tests
859
+ if __name__ == '__main__':
860
+ import unittest, math
861
+
862
+ class MaxWeightMatchingTests(unittest.TestCase):
863
+
864
+ def test10_empty(self):
865
+ # empty input graph
866
+ self.assertEqual(maxWeightMatching([]), [])
867
+
868
+ def test11_singleedge(self):
869
+ # single edge
870
+ self.assertEqual(maxWeightMatching([ (0,1,1) ]), [1, 0])
871
+
872
+ def test12(self):
873
+ self.assertEqual(maxWeightMatching([ (1,2,10), (2,3,11) ]), [ -1, -1, 3, 2 ])
874
+
875
+ def test13(self):
876
+ self.assertEqual(maxWeightMatching([ (1,2,5), (2,3,11), (3,4,5) ]), [ -1, -1, 3, 2, -1 ])
877
+
878
+ def test14_maxcard(self):
879
+ # maximum cardinality
880
+ self.assertEqual(maxWeightMatching([ (1,2,5), (2,3,11), (3,4,5) ], True), [ -1, 2, 1, 4, 3 ])
881
+
882
+ def test15_float(self):
883
+ # floating point weigths
884
+ self.assertEqual(maxWeightMatching([ (1,2,math.pi), (2,3,math.exp(1)), (1,3,3.0), (1,4,math.sqrt(2.0)) ]), [ -1, 4, 3, 2, 1 ])
885
+
886
+ def test16_negative(self):
887
+ # negative weights
888
+ self.assertEqual(maxWeightMatching([ (1,2,2), (1,3,-2), (2,3,1), (2,4,-1), (3,4,-6) ], False), [ -1, 2, 1, -1, -1 ])
889
+ self.assertEqual(maxWeightMatching([ (1,2,2), (1,3,-2), (2,3,1), (2,4,-1), (3,4,-6) ], True), [ -1, 3, 4, 1, 2 ])
890
+
891
+ def test20_sblossom(self):
892
+ # create S-blossom and use it for augmentation
893
+ self.assertEqual(maxWeightMatching([ (1,2,8), (1,3,9), (2,3,10), (3,4,7) ]), [ -1, 2, 1, 4, 3 ])
894
+ self.assertEqual(maxWeightMatching([ (1,2,8), (1,3,9), (2,3,10), (3,4,7), (1,6,5), (4,5,6) ]), [ -1, 6, 3, 2, 5, 4, 1 ])
895
+
896
+ def test21_tblossom(self):
897
+ # create S-blossom, relabel as T-blossom, use for augmentation
898
+ self.assertEqual(maxWeightMatching([ (1,2,9), (1,3,8), (2,3,10), (1,4,5), (4,5,4), (1,6,3) ]), [ -1, 6, 3, 2, 5, 4, 1 ])
899
+ self.assertEqual(maxWeightMatching([ (1,2,9), (1,3,8), (2,3,10), (1,4,5), (4,5,3), (1,6,4) ]), [ -1, 6, 3, 2, 5, 4, 1 ])
900
+ self.assertEqual(maxWeightMatching([ (1,2,9), (1,3,8), (2,3,10), (1,4,5), (4,5,3), (3,6,4) ]), [ -1, 2, 1, 6, 5, 4, 3 ])
901
+
902
+ def test22_s_nest(self):
903
+ # create nested S-blossom, use for augmentation
904
+ self.assertEqual(maxWeightMatching([ (1,2,9), (1,3,9), (2,3,10), (2,4,8), (3,5,8), (4,5,10), (5,6,6) ]), [ -1, 3, 4, 1, 2, 6, 5 ])
905
+
906
+ def test23_s_relabel_nest(self):
907
+ # create S-blossom, relabel as S, include in nested S-blossom
908
+ self.assertEqual(maxWeightMatching([ (1,2,10), (1,7,10), (2,3,12), (3,4,20), (3,5,20), (4,5,25), (5,6,10), (6,7,10), (7,8,8) ]), [ -1, 2, 1, 4, 3, 6, 5, 8, 7 ])
909
+
910
+ def test24_s_nest_expand(self):
911
+ # create nested S-blossom, augment, expand recursively
912
+ self.assertEqual(maxWeightMatching([ (1,2,8), (1,3,8), (2,3,10), (2,4,12), (3,5,12), (4,5,14), (4,6,12), (5,7,12), (6,7,14), (7,8,12) ]), [ -1, 2, 1, 5, 6, 3, 4, 8, 7 ])
913
+
914
+ def test25_s_t_expand(self):
915
+ # create S-blossom, relabel as T, expand
916
+ self.assertEqual(maxWeightMatching([ (1,2,23), (1,5,22), (1,6,15), (2,3,25), (3,4,22), (4,5,25), (4,8,14), (5,7,13) ]), [ -1, 6, 3, 2, 8, 7, 1, 5, 4 ])
917
+
918
+ def test26_s_nest_t_expand(self):
919
+ # create nested S-blossom, relabel as T, expand
920
+ self.assertEqual(maxWeightMatching([ (1,2,19), (1,3,20), (1,8,8), (2,3,25), (2,4,18), (3,5,18), (4,5,13), (4,7,7), (5,6,7) ]), [ -1, 8, 3, 2, 7, 6, 5, 4, 1 ])
921
+
922
+ def test30_tnasty_expand(self):
923
+ # create blossom, relabel as T in more than one way, expand, augment
924
+ self.assertEqual(maxWeightMatching([ (1,2,45), (1,5,45), (2,3,50), (3,4,45), (4,5,50), (1,6,30), (3,9,35), (4,8,35), (5,7,26), (9,10,5) ]), [ -1, 6, 3, 2, 8, 7, 1, 5, 4, 10, 9 ])
925
+
926
+ def test31_tnasty2_expand(self):
927
+ # again but slightly different
928
+ self.assertEqual(maxWeightMatching([ (1,2,45), (1,5,45), (2,3,50), (3,4,45), (4,5,50), (1,6,30), (3,9,35), (4,8,26), (5,7,40), (9,10,5) ]), [ -1, 6, 3, 2, 8, 7, 1, 5, 4, 10, 9 ])
929
+
930
+ def test32_t_expand_leastslack(self):
931
+ # create blossom, relabel as T, expand such that a new least-slack S-to-free edge is produced, augment
932
+ self.assertEqual(maxWeightMatching([ (1,2,45), (1,5,45), (2,3,50), (3,4,45), (4,5,50), (1,6,30), (3,9,35), (4,8,28), (5,7,26), (9,10,5) ]), [ -1, 6, 3, 2, 8, 7, 1, 5, 4, 10, 9 ])
933
+
934
+ def test33_nest_tnasty_expand(self):
935
+ # create nested blossom, relabel as T in more than one way, expand outer blossom such that inner blossom ends up on an augmenting path
936
+ self.assertEqual(maxWeightMatching([ (1,2,45), (1,7,45), (2,3,50), (3,4,45), (4,5,95), (4,6,94), (5,6,94), (6,7,50), (1,8,30), (3,11,35), (5,9,36), (7,10,26), (11,12,5) ]), [ -1, 8, 3, 2, 6, 9, 4, 10, 1, 5, 7, 12, 11 ])
937
+
938
+ def test34_nest_relabel_expand(self):
939
+ # create nested S-blossom, relabel as S, expand recursively
940
+ self.assertEqual(maxWeightMatching([ (1,2,40), (1,3,40), (2,3,60), (2,4,55), (3,5,55), (4,5,50), (1,8,15), (5,7,30), (7,6,10), (8,10,10), (4,9,30) ]), [ -1, 2, 1, 5, 9, 3, 7, 6, 10, 4, 8 ])
941
+
942
+ CHECK_DELTA = True
943
+ unittest.main()
944
+
945
+ # end