graph_matching 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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