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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/graph_matching.gemspec +13 -11
- data/lib/graph_matching/version.rb +1 -1
- metadata +2 -77
- data/benchmark/mcm_bipartite/complete_bigraphs/benchmark.rb +0 -33
- data/benchmark/mcm_bipartite/complete_bigraphs/compare.gnuplot +0 -19
- data/benchmark/mcm_bipartite/complete_bigraphs/edges_times_vertexes.data +0 -500
- data/benchmark/mcm_bipartite/complete_bigraphs/plot.gnuplot +0 -21
- data/benchmark/mcm_bipartite/complete_bigraphs/plot.png +0 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/time.data +0 -499
- data/benchmark/mcm_general/complete_graphs/benchmark.rb +0 -30
- data/benchmark/mcm_general/complete_graphs/plot.gnuplot +0 -19
- data/benchmark/mcm_general/complete_graphs/plot.png +0 -0
- data/benchmark/mcm_general/complete_graphs/time.data +0 -499
- data/benchmark/mcm_general/complete_graphs/v_cubed.data +0 -500
- data/benchmark/mwm_bipartite/complete_bigraphs/benchmark.rb +0 -43
- data/benchmark/mwm_bipartite/complete_bigraphs/nmN.data +0 -499
- data/benchmark/mwm_bipartite/complete_bigraphs/nmN.xlsx +0 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/plot.gnuplot +0 -22
- data/benchmark/mwm_bipartite/complete_bigraphs/plot.png +0 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/time.data +0 -299
- data/benchmark/mwm_bipartite/misc/calc_d2/benchmark.rb +0 -25
- data/benchmark/mwm_general/complete_graphs/benchmark.rb +0 -32
- data/benchmark/mwm_general/complete_graphs/compare.gnuplot +0 -19
- data/benchmark/mwm_general/complete_graphs/mn_log_n.data +0 -299
- data/benchmark/mwm_general/complete_graphs/mn_log_n.xlsx +0 -0
- data/benchmark/mwm_general/complete_graphs/plot.gnuplot +0 -22
- data/benchmark/mwm_general/complete_graphs/plot.png +0 -0
- data/benchmark/mwm_general/complete_graphs/time.data +0 -299
- data/benchmark/mwm_general/incomplete_graphs/benchmark.rb +0 -38
- data/benchmark/mwm_general/incomplete_graphs/plot.gnuplot +0 -22
- data/benchmark/mwm_general/incomplete_graphs/plot.png +0 -0
- data/benchmark/mwm_general/incomplete_graphs/time_10_pct.data +0 -299
- data/benchmark/mwm_general/incomplete_graphs/time_20_pct.data +0 -299
- data/benchmark/mwm_general/incomplete_graphs/time_30_pct.data +0 -299
- data/profile/mcm_bipartite/compare.sh +0 -15
- data/profile/mcm_bipartite/publish.sh +0 -12
- data/profile/mwm_general/compare.sh +0 -15
- data/profile/mwm_general/profile.rb +0 -28
- data/profile/mwm_general/publish.sh +0 -12
- 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 +0 -86
- 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 +0 -19
- data/research/van_rantwijk/mwmatching.py +0 -945
- data/spec/graph_matching/algorithm/matching_algorithm_spec.rb +0 -14
- data/spec/graph_matching/algorithm/mcm_bipartite_spec.rb +0 -98
- data/spec/graph_matching/algorithm/mcm_general_spec.rb +0 -159
- data/spec/graph_matching/algorithm/mwm_bipartite_spec.rb +0 -82
- data/spec/graph_matching/algorithm/mwm_general_spec.rb +0 -442
- data/spec/graph_matching/graph/bigraph_spec.rb +0 -73
- data/spec/graph_matching/graph/graph_spec.rb +0 -53
- data/spec/graph_matching/graph/weighted_spec.rb +0 -29
- data/spec/graph_matching/integer_vertexes_spec.rb +0 -21
- data/spec/graph_matching/matching_spec.rb +0 -89
- data/spec/graph_matching/visualize_spec.rb +0 -38
- data/spec/graph_matching_spec.rb +0 -9
- 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"
|
data/research/1965_edmonds.pdf
DELETED
Binary file
|
Binary file
|
data/research/1976_gabow.pdf
DELETED
Binary file
|
Binary file
|
data/research/1985_gabow.pdf
DELETED
Binary file
|
data/research/2002_tarjan.pdf
DELETED
Binary file
|
data/research/2013_zwick.pdf
DELETED
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?)
|
data/research/goodwin.pdf
DELETED
Binary file
|
Binary file
|
data/research/kusner.pdf
DELETED
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
|