graph_matching 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rubocop.yml +112 -0
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +205 -0
- data/Rakefile +9 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/benchmark.rb +33 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/compare.gnuplot +19 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/edges_times_vertexes.data +500 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/plot.gnuplot +21 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/plot.png +0 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/time.data +499 -0
- data/benchmark/mcm_general/complete_graphs/benchmark.rb +30 -0
- data/benchmark/mcm_general/complete_graphs/plot.gnuplot +19 -0
- data/benchmark/mcm_general/complete_graphs/plot.png +0 -0
- data/benchmark/mcm_general/complete_graphs/time.data +499 -0
- data/benchmark/mcm_general/complete_graphs/v_cubed.data +500 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/benchmark.rb +43 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/nmN.data +499 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/nmN.xlsx +0 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/plot.gnuplot +22 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/plot.png +0 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/time.data +299 -0
- data/benchmark/mwm_bipartite/misc/calc_d2/benchmark.rb +29 -0
- data/benchmark/mwm_general/complete_graphs/benchmark.rb +32 -0
- data/benchmark/mwm_general/complete_graphs/compare.gnuplot +19 -0
- data/benchmark/mwm_general/complete_graphs/mn_log_n.data +299 -0
- data/benchmark/mwm_general/complete_graphs/mn_log_n.xlsx +0 -0
- data/benchmark/mwm_general/complete_graphs/plot.gnuplot +22 -0
- data/benchmark/mwm_general/complete_graphs/plot.png +0 -0
- data/benchmark/mwm_general/complete_graphs/time.data +299 -0
- data/benchmark/mwm_general/incomplete_graphs/benchmark.rb +39 -0
- data/benchmark/mwm_general/incomplete_graphs/plot.gnuplot +22 -0
- data/benchmark/mwm_general/incomplete_graphs/plot.png +0 -0
- data/benchmark/mwm_general/incomplete_graphs/time_10_pct.data +299 -0
- data/benchmark/mwm_general/incomplete_graphs/time_20_pct.data +299 -0
- data/benchmark/mwm_general/incomplete_graphs/time_30_pct.data +299 -0
- data/graph_matching.gemspec +35 -0
- data/lib/graph_matching.rb +15 -0
- data/lib/graph_matching/algorithm/matching_algorithm.rb +23 -0
- data/lib/graph_matching/algorithm/mcm_bipartite.rb +118 -0
- data/lib/graph_matching/algorithm/mcm_general.rb +289 -0
- data/lib/graph_matching/algorithm/mwm_bipartite.rb +147 -0
- data/lib/graph_matching/algorithm/mwm_general.rb +1086 -0
- data/lib/graph_matching/algorithm/mwmg_delta_assertions.rb +94 -0
- data/lib/graph_matching/assertion.rb +41 -0
- data/lib/graph_matching/core_ext/set.rb +36 -0
- data/lib/graph_matching/directed_edge_set.rb +31 -0
- data/lib/graph_matching/errors.rb +23 -0
- data/lib/graph_matching/graph/bigraph.rb +37 -0
- data/lib/graph_matching/graph/graph.rb +63 -0
- data/lib/graph_matching/graph/weighted.rb +112 -0
- data/lib/graph_matching/graph/weighted_bigraph.rb +17 -0
- data/lib/graph_matching/graph/weighted_graph.rb +17 -0
- data/lib/graph_matching/integer_vertexes.rb +29 -0
- data/lib/graph_matching/matching.rb +120 -0
- data/lib/graph_matching/ordered_set.rb +59 -0
- data/lib/graph_matching/version.rb +6 -0
- data/lib/graph_matching/visualize.rb +93 -0
- data/profile/mcm_bipartite/compare.sh +15 -0
- data/profile/mcm_bipartite/publish.sh +12 -0
- data/profile/mwm_general/compare.sh +15 -0
- data/profile/mwm_general/profile.rb +28 -0
- data/profile/mwm_general/publish.sh +12 -0
- data/research/1965_edmonds.pdf +0 -0
- data/research/1975_even_kariv.pdf +0 -0
- data/research/1976_gabow.pdf +0 -0
- data/research/1980_micali_vazirani.pdf +0 -0
- data/research/1985_gabow.pdf +0 -0
- data/research/2002_tarjan.pdf +0 -0
- data/research/2013_zwick.pdf +0 -0
- data/research/examples/unweighted_general/1.txt +86 -0
- data/research/goodwin.pdf +0 -0
- data/research/kavathekar-scribe.pdf +0 -0
- data/research/kusner.pdf +0 -0
- data/research/van_rantwijk/mwm_example.py +19 -0
- data/research/van_rantwijk/mwmatching.py +945 -0
- data/spec/graph_matching/algorithm/matching_algorithm_spec.rb +14 -0
- data/spec/graph_matching/algorithm/mcm_bipartite_spec.rb +98 -0
- data/spec/graph_matching/algorithm/mcm_general_spec.rb +159 -0
- data/spec/graph_matching/algorithm/mwm_bipartite_spec.rb +82 -0
- data/spec/graph_matching/algorithm/mwm_general_spec.rb +439 -0
- data/spec/graph_matching/graph/bigraph_spec.rb +73 -0
- data/spec/graph_matching/graph/graph_spec.rb +53 -0
- data/spec/graph_matching/graph/weighted_spec.rb +29 -0
- data/spec/graph_matching/integer_vertexes_spec.rb +21 -0
- data/spec/graph_matching/matching_spec.rb +89 -0
- data/spec/graph_matching/visualize_spec.rb +38 -0
- data/spec/graph_matching_spec.rb +9 -0
- data/spec/spec_helper.rb +26 -0
- metadata +263 -0
Binary file
|
Binary file
|
data/research/kusner.pdf
ADDED
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
|