graph_matching 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/graph_matching.gemspec +13 -11
  4. data/lib/graph_matching/version.rb +1 -1
  5. metadata +2 -77
  6. data/benchmark/mcm_bipartite/complete_bigraphs/benchmark.rb +0 -33
  7. data/benchmark/mcm_bipartite/complete_bigraphs/compare.gnuplot +0 -19
  8. data/benchmark/mcm_bipartite/complete_bigraphs/edges_times_vertexes.data +0 -500
  9. data/benchmark/mcm_bipartite/complete_bigraphs/plot.gnuplot +0 -21
  10. data/benchmark/mcm_bipartite/complete_bigraphs/plot.png +0 -0
  11. data/benchmark/mcm_bipartite/complete_bigraphs/time.data +0 -499
  12. data/benchmark/mcm_general/complete_graphs/benchmark.rb +0 -30
  13. data/benchmark/mcm_general/complete_graphs/plot.gnuplot +0 -19
  14. data/benchmark/mcm_general/complete_graphs/plot.png +0 -0
  15. data/benchmark/mcm_general/complete_graphs/time.data +0 -499
  16. data/benchmark/mcm_general/complete_graphs/v_cubed.data +0 -500
  17. data/benchmark/mwm_bipartite/complete_bigraphs/benchmark.rb +0 -43
  18. data/benchmark/mwm_bipartite/complete_bigraphs/nmN.data +0 -499
  19. data/benchmark/mwm_bipartite/complete_bigraphs/nmN.xlsx +0 -0
  20. data/benchmark/mwm_bipartite/complete_bigraphs/plot.gnuplot +0 -22
  21. data/benchmark/mwm_bipartite/complete_bigraphs/plot.png +0 -0
  22. data/benchmark/mwm_bipartite/complete_bigraphs/time.data +0 -299
  23. data/benchmark/mwm_bipartite/misc/calc_d2/benchmark.rb +0 -25
  24. data/benchmark/mwm_general/complete_graphs/benchmark.rb +0 -32
  25. data/benchmark/mwm_general/complete_graphs/compare.gnuplot +0 -19
  26. data/benchmark/mwm_general/complete_graphs/mn_log_n.data +0 -299
  27. data/benchmark/mwm_general/complete_graphs/mn_log_n.xlsx +0 -0
  28. data/benchmark/mwm_general/complete_graphs/plot.gnuplot +0 -22
  29. data/benchmark/mwm_general/complete_graphs/plot.png +0 -0
  30. data/benchmark/mwm_general/complete_graphs/time.data +0 -299
  31. data/benchmark/mwm_general/incomplete_graphs/benchmark.rb +0 -38
  32. data/benchmark/mwm_general/incomplete_graphs/plot.gnuplot +0 -22
  33. data/benchmark/mwm_general/incomplete_graphs/plot.png +0 -0
  34. data/benchmark/mwm_general/incomplete_graphs/time_10_pct.data +0 -299
  35. data/benchmark/mwm_general/incomplete_graphs/time_20_pct.data +0 -299
  36. data/benchmark/mwm_general/incomplete_graphs/time_30_pct.data +0 -299
  37. data/profile/mcm_bipartite/compare.sh +0 -15
  38. data/profile/mcm_bipartite/publish.sh +0 -12
  39. data/profile/mwm_general/compare.sh +0 -15
  40. data/profile/mwm_general/profile.rb +0 -28
  41. data/profile/mwm_general/publish.sh +0 -12
  42. data/research/1965_edmonds.pdf +0 -0
  43. data/research/1975_even_kariv.pdf +0 -0
  44. data/research/1976_gabow.pdf +0 -0
  45. data/research/1980_micali_vazirani.pdf +0 -0
  46. data/research/1985_gabow.pdf +0 -0
  47. data/research/2002_tarjan.pdf +0 -0
  48. data/research/2013_zwick.pdf +0 -0
  49. data/research/examples/unweighted_general/1.txt +0 -86
  50. data/research/goodwin.pdf +0 -0
  51. data/research/kavathekar-scribe.pdf +0 -0
  52. data/research/kusner.pdf +0 -0
  53. data/research/van_rantwijk/mwm_example.py +0 -19
  54. data/research/van_rantwijk/mwmatching.py +0 -945
  55. data/spec/graph_matching/algorithm/matching_algorithm_spec.rb +0 -14
  56. data/spec/graph_matching/algorithm/mcm_bipartite_spec.rb +0 -98
  57. data/spec/graph_matching/algorithm/mcm_general_spec.rb +0 -159
  58. data/spec/graph_matching/algorithm/mwm_bipartite_spec.rb +0 -82
  59. data/spec/graph_matching/algorithm/mwm_general_spec.rb +0 -442
  60. data/spec/graph_matching/graph/bigraph_spec.rb +0 -73
  61. data/spec/graph_matching/graph/graph_spec.rb +0 -53
  62. data/spec/graph_matching/graph/weighted_spec.rb +0 -29
  63. data/spec/graph_matching/integer_vertexes_spec.rb +0 -21
  64. data/spec/graph_matching/matching_spec.rb +0 -89
  65. data/spec/graph_matching/visualize_spec.rb +0 -38
  66. data/spec/graph_matching_spec.rb +0 -9
  67. data/spec/spec_helper.rb +0 -26
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- BENCHMARK_DIR='benchmark/mcm_bipartite/complete_bigraphs'
4
-
5
- if [ ! -d "$BENCHMARK_DIR" ]; then
6
- echo "Directory not found: $BENCHMARK_DIR" 1>&2
7
- exit 1
8
- fi
9
-
10
- echo "Benchmarking .."
11
- ruby -I lib "$BENCHMARK_DIR/benchmark.rb" > "$BENCHMARK_DIR/time2.data"
12
-
13
- echo "Plotting .."
14
- gnuplot "$BENCHMARK_DIR/compare.gnuplot"
15
- open "$BENCHMARK_DIR/plot_compare.png"
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- BENCHMARK_DIR='benchmark/mcm_bipartite/complete_bigraphs'
4
-
5
- if [ ! -d "$BENCHMARK_DIR" ]; then
6
- echo "Directory not found: $BENCHMARK_DIR" 1>&2
7
- exit 1
8
- fi
9
-
10
- rm "$BENCHMARK_DIR/plot_compare.png"
11
- mv "$BENCHMARK_DIR/time2.data" "$BENCHMARK_DIR/time.data"
12
- gnuplot "$BENCHMARK_DIR/plot.gnuplot"
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- BENCHMARK_DIR='benchmark/mwm_general/complete_graphs'
4
-
5
- if [ ! -d "$BENCHMARK_DIR" ]; then
6
- echo "Directory not found: $BENCHMARK_DIR" 1>&2
7
- exit 1
8
- fi
9
-
10
- echo "Benchmarking .."
11
- ruby -I lib "$BENCHMARK_DIR/benchmark.rb" > "$BENCHMARK_DIR/time2.data"
12
-
13
- echo "Plotting .."
14
- gnuplot "$BENCHMARK_DIR/compare.gnuplot"
15
- open "$BENCHMARK_DIR/plot_compare.png"
@@ -1,28 +0,0 @@
1
- # No shebang here. Run with:
2
- #
3
- # ruby -I lib profile/mwm_general/profile.rb
4
-
5
- require 'graph_matching'
6
- require 'ruby-prof'
7
-
8
- def complete_graph(n)
9
- g = GraphMatching::Graph::WeightedGraph.new
10
- n_edges = (1..n - 1).reduce(:+)
11
- 0.upto(n - 2) do |i|
12
- (i + 1).upto(n - 1) do |j|
13
- g.add_edge(i, j)
14
- g.set_w([i, j], rand(n_edges))
15
- end
16
- end
17
- g
18
- end
19
-
20
- g = complete_graph(100)
21
- GC.disable
22
- RubyProf.start
23
- g.maximum_weighted_matching(true)
24
- result = RubyProf.stop
25
- GC.enable
26
-
27
- printer = RubyProf::FlatPrinter.new(result)
28
- printer.print(STDOUT)
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- BENCHMARK_DIR='benchmark/mwm_general/complete_graphs'
4
-
5
- if [ ! -d "$BENCHMARK_DIR" ]; then
6
- echo "Directory not found: $BENCHMARK_DIR" 1>&2
7
- exit 1
8
- fi
9
-
10
- rm "$BENCHMARK_DIR/plot_compare.png"
11
- mv "$BENCHMARK_DIR/time2.data" "$BENCHMARK_DIR/time.data"
12
- gnuplot "$BENCHMARK_DIR/plot.gnuplot"
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,86 +0,0 @@
1
- Stage 1
2
-
3
- 5 -- 7
4
- / || ||
5
- 1 = 3 || o
6
- / \ ||
7
- r 6
8
- \ /
9
- 2 = 4
10
- /
11
- a
12
-
13
- S = [ 1 by r, 2 by r, 5 by 3, 6 by 3 ]
14
- T = [ 3 by 1, 4 by 1 ]
15
-
16
- Stage 2
17
-
18
- 1 = B -- 7
19
- / | ||
20
- r | o
21
- \ |
22
- 2 = 4
23
- /
24
- a
25
-
26
- S = [ 1 by r, 2 by r, 5 by 3, 6 by 3 ]
27
- T = [ 3 by 1, 4 by 1 ]
28
-
29
- Stage 3
30
-
31
- B -- 7
32
- / ||
33
- a o
34
-
35
- Find augmenting path from r, *through B* to a
36
-
37
- 1. AP = (a,B)
38
- 1. expand B
39
-
40
- 1 = B -- 7
41
- / | ||
42
- r | o
43
- \ |
44
- 2 = 4
45
- /
46
- a
47
-
48
- 1. Kusner says: "propagating the augmenting path through the expansion steps"
49
- 1. AP = (a,2), etc.. but how to find "etc"? We know we want an
50
- alternating path, so starting at 2, find a matched edge.
51
- 1. AP = a2, 24
52
- 1. AP = a2, 24, 4B
53
- 1. B is a blossom. Expand it.
54
-
55
- 5 -- 7
56
- / || ||
57
- 1 = 3 || o
58
- / \ ||
59
- r 6
60
- \ /
61
- 2 = 4
62
- /
63
- a
64
-
65
- 1. AP = a2, 24, 46
66
- 1. AP = a2, 24, 46, 65
67
- 1. Should we follow (5,3) or (5,7), or both?
68
- 1. Following (5,7) doesn't reach r, and if we follow it,
69
- either depth-first or breadth-first, we'll learn that.
70
- 1. AP = a2, 24, 46, 65, 53
71
- 1. AP = a2, 24, 46, 65, 53, 31
72
- 1. AP = a2, 24, 46, 65, 53, 31, 1r
73
- 1. Augmenting path: a, 2, 4, 6, 5, 3, 1, r
74
-
75
- 5 -- 7
76
- //| ||
77
- 1 - 3 | o
78
- // \|
79
- r 6
80
- \ //
81
- 2 - 4
82
- //
83
- a
84
-
85
- 1. size of matching = 5. Decide is maximum cardinality
86
- matching. (How?)
Binary file
Binary file
@@ -1,19 +0,0 @@
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)
@@ -1,945 +0,0 @@
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