|
| 1 | +function [val m1 m2 mi]=bipartite_matching(varargin) |
| 2 | +% BIPARTITE_MATCHING Solve a maximum weight bipartite matching problem |
| 3 | +% |
| 4 | +% [val m1 m2]=bipartite_matching(A) for a rectangular matrix A |
| 5 | +% [val m1 m2 mi]=bipartite_matching(x,ei,ej,n,m) for a matrix stored |
| 6 | +% in triplet format. This call also returns a matching indicator mi so |
| 7 | +% that val = x'*mi. |
| 8 | +% |
| 9 | +% The maximum weight bipartite matching problem tries to pick out elements |
| 10 | +% from A such that each row and column get only a single non-zero but the |
| 11 | +% sum of all the chosen elements is as large as possible. |
| 12 | +% |
| 13 | +% This function is slightly atypical for a graph library, because it will |
| 14 | +% be primarily used on rectangular inputs. However, these rectangular |
| 15 | +% inputs model bipartite graphs and we take advantage of that stucture in |
| 16 | +% this code. The underlying graph adjency matrix is |
| 17 | +% G = spaugment(A,0); |
| 18 | +% where A is the rectangular input to the bipartite_matching function. |
| 19 | +% |
| 20 | +% Matlab already has the dmperm function that computes a maximum |
| 21 | +% cardinality matching between the rows and the columns. This function |
| 22 | +% gives us the maximum weight matching instead. For unweighted graphs, the |
| 23 | +% two functions are equivalent. |
| 24 | +% |
| 25 | +% Note: If ei and ej contain duplicate edges, the results of this function |
| 26 | +% are incorrect. |
| 27 | +% |
| 28 | +% See also DMPERM |
| 29 | +% |
| 30 | +% Example: |
| 31 | +% A = rand(10,8); % bipartite matching between random data |
| 32 | +% [val mi mj] = bipartite_matching(A); |
| 33 | +% val |
| 34 | + |
| 35 | +% David F. Gleich and Ying Wang |
| 36 | +% Copyright, Stanford University, 2008-2009 |
| 37 | +% Computational Approaches to Digital Stewardship |
| 38 | + |
| 39 | +% 2008-04-24: Initial coding (copy from Ying Wang matching_sparse_mex.cpp) |
| 40 | +% 2008-11-15: Added triplet input/output |
| 41 | +% 2009-04-30: Modified for gaimc library |
| 42 | +% 2009-05-15: Fixed error with empty inputs and triple added example. |
| 43 | + |
| 44 | +[rp ci ai tripi n m] = bipartite_matching_setup(varargin{:}); |
| 45 | + |
| 46 | +if isempty(tripi) |
| 47 | + error(nargoutchk(0,3,nargout,'struct')); |
| 48 | +else |
| 49 | + error(nargoutchk(0,4,nargout,'struct')); |
| 50 | +end |
| 51 | + |
| 52 | + |
| 53 | +if ~isempty(tripi) && nargout>3 |
| 54 | + [val m1 m2 mi] = bipartite_matching_primal_dual(rp, ci, ai, tripi, n, m); |
| 55 | +else |
| 56 | + [val m1 m2] = bipartite_matching_primal_dual(rp, ci, ai, tripi, n, m); |
| 57 | +end |
| 58 | + |
| 59 | +function [rp ci ai tripi n m]= bipartite_matching_setup(A,ei,ej,n,m) |
| 60 | +% convert the input |
| 61 | + |
| 62 | +if nargin == 1 |
| 63 | + if isstruct(A) |
| 64 | + [nzi nzj nzv]=csr_to_sparse(A.rp,A.ci,A.ai); |
| 65 | + else |
| 66 | + [nzi nzj nzv]=find(A); |
| 67 | + end |
| 68 | + [n m]=size(A); |
| 69 | + triplet = 0; |
| 70 | +elseif nargin >= 3 && nargin <= 5 |
| 71 | + nzi = ei; |
| 72 | + nzj = ej; |
| 73 | + nzv = A; |
| 74 | + if ~exist('n','var') || isempty(n), n = max(nzi); end |
| 75 | + if ~exist('m','var') || isempty(m), m = max(nzj); end |
| 76 | + triplet = 1; |
| 77 | +else |
| 78 | + error(nargchk(3,5,nargin,'struct')); |
| 79 | +end |
| 80 | +nedges = length(nzi); |
| 81 | + |
| 82 | +rp = ones(n+1,1); % csr matrix with extra edges |
| 83 | +ci = zeros(nedges+n,1); |
| 84 | +ai = zeros(nedges+n,1); |
| 85 | +if triplet, tripi = zeros(nedges+n,1); % triplet index |
| 86 | +else tripi = []; |
| 87 | +end |
| 88 | + |
| 89 | +% |
| 90 | +% 1. build csr representation with a set of extra edges from vertex i to |
| 91 | +% vertex m+i |
| 92 | +% |
| 93 | +rp(1)=0; |
| 94 | +for i=1:nedges |
| 95 | + rp(nzi(i)+1)=rp(nzi(i)+1)+1; |
| 96 | +end |
| 97 | +rp=cumsum(rp); |
| 98 | +for i=1:nedges |
| 99 | + if triplet, tripi(rp(nzi(i))+1)=i; end % triplet index |
| 100 | + ai(rp(nzi(i))+1)=nzv(i); |
| 101 | + ci(rp(nzi(i))+1)=nzj(i); |
| 102 | + rp(nzi(i))=rp(nzi(i))+1; |
| 103 | +end |
| 104 | +for i=1:n % add the extra edges |
| 105 | + if triplet, tripi(rp(i)+1)=-1; end % triplet index |
| 106 | + ai(rp(i)+1)=0; |
| 107 | + ci(rp(i)+1)=m+i; |
| 108 | + rp(i)=rp(i)+1; |
| 109 | +end |
| 110 | +% restore the row pointer array |
| 111 | +for i=n:-1:1 |
| 112 | + rp(i+1)=rp(i); |
| 113 | +end |
| 114 | +rp(1)=0; |
| 115 | +rp=rp+1; |
| 116 | + |
| 117 | +% |
| 118 | +% 1a. check for duplicates in the data |
| 119 | +% |
| 120 | +colind = false(m+n,1); |
| 121 | +for i=1:n |
| 122 | + for rpi=rp(i):rp(i+1)-1 |
| 123 | + if colind(ci(rpi)), error('bipartite_matching:duplicateEdge',... |
| 124 | + 'duplicate edge detected (%i,%i)',i,ci(rpi)); |
| 125 | + end |
| 126 | + colind(ci(rpi))=1; |
| 127 | + end |
| 128 | + for rpi=rp(i):rp(i+1)-1, colind(ci(rpi))=0; end % reset indicator |
| 129 | +end |
| 130 | + |
| 131 | + |
| 132 | +function [val m1 m2 mi]=bipartite_matching_primal_dual(... |
| 133 | + rp, ci, ai, tripi, n, m) |
| 134 | +% BIPARTITE_MATCHING_PRIMAL_DUAL |
| 135 | + |
| 136 | +alpha=zeros(n,1); % variables used for the primal-dual algorithm |
| 137 | +beta=zeros(n+m,1); |
| 138 | +queue=zeros(n,1); |
| 139 | +t=zeros(n+m,1); |
| 140 | +match1=zeros(n,1); |
| 141 | +match2=zeros(n+m,1); |
| 142 | +tmod = zeros(n+m,1); |
| 143 | +ntmod=0; |
| 144 | + |
| 145 | + |
| 146 | +% |
| 147 | +% initialize the primal and dual variables |
| 148 | +% |
| 149 | +for i=1:n |
| 150 | + for rpi=rp(i):rp(i+1)-1 |
| 151 | + if ai(rpi) > alpha(i), alpha(i)=ai(rpi); end |
| 152 | + end |
| 153 | +end |
| 154 | +% dual variables (beta) are initialized to 0 already |
| 155 | +% match1 and match2 are both 0, which indicates no matches |
| 156 | +i=1; |
| 157 | +while i<=n |
| 158 | + % repeat the problem for n stages |
| 159 | + |
| 160 | + % clear t(j) |
| 161 | + for j=1:ntmod, t(tmod(j))=0; end |
| 162 | + ntmod=0; |
| 163 | + |
| 164 | + |
| 165 | + % add i to the stack |
| 166 | + head=1; tail=1; |
| 167 | + queue(head)=i; % add i to the head of the queue |
| 168 | + while head <= tail && match1(i)==0 |
| 169 | + k=queue(head); |
| 170 | + for rpi=rp(k):rp(k+1)-1 |
| 171 | + j = ci(rpi); |
| 172 | + if ai(rpi) < alpha(k)+beta(j) - 1e-8, continue; end % skip if tight |
| 173 | + if t(j)==0, |
| 174 | + tail=tail+1; queue(tail)=match2(j); |
| 175 | + t(j)=k; |
| 176 | + ntmod=ntmod+1; tmod(ntmod)=j; |
| 177 | + if match2(j)<1, |
| 178 | + while j>0, |
| 179 | + match2(j)=t(j); |
| 180 | + k=t(j); |
| 181 | + temp=match1(k); |
| 182 | + match1(k)=j; |
| 183 | + j=temp; |
| 184 | + end |
| 185 | + break; % we found an alternating path |
| 186 | + end |
| 187 | + end |
| 188 | + end |
| 189 | + head=head+1; |
| 190 | + end |
| 191 | + |
| 192 | + if match1(i) < 1, % still not matched, so update primal, dual and repeat |
| 193 | + theta=inf; |
| 194 | + for j=1:head-1 |
| 195 | + t1=queue(j); |
| 196 | + for rpi=rp(t1):rp(t1+1)-1 |
| 197 | + t2=ci(rpi); |
| 198 | + if t(t2) == 0 && alpha(t1) + beta(t2) - ai(rpi) < theta, |
| 199 | + theta = alpha(t1) + beta(t2) - ai(rpi); |
| 200 | + end |
| 201 | + end |
| 202 | + end |
| 203 | + |
| 204 | + for j=1:head-1, alpha(queue(j)) = alpha(queue(j)) - theta; end |
| 205 | + |
| 206 | + for j=1:ntmod, beta(tmod(j)) = beta(tmod(j)) + theta; end |
| 207 | + |
| 208 | + continue; |
| 209 | + end |
| 210 | + |
| 211 | + i=i+1; % increment i |
| 212 | +end |
| 213 | + |
| 214 | +val=0; |
| 215 | +for i=1:n |
| 216 | + for rpi=rp(i):rp(i+1)-1 |
| 217 | + if ci(rpi)==match1(i), val=val+ai(rpi); end |
| 218 | + end |
| 219 | +end |
| 220 | +noute = 0; % count number of output edges |
| 221 | +for i=1:n |
| 222 | + if match1(i)<=m, noute=noute+1; end |
| 223 | +end |
| 224 | +m1=zeros(noute,1); m2=m1; % copy over the 0 array |
| 225 | +noute=1; |
| 226 | +for i=1:n |
| 227 | + if match1(i)<=m, m1(noute)=i; m2(noute)=match1(i);noute=noute+1; end |
| 228 | +end |
| 229 | + |
| 230 | +if nargout>3 |
| 231 | + mi= false(length(tripi)-n,1); |
| 232 | + for i=1:n |
| 233 | + for rpi=rp(i):rp(i+1)-1 |
| 234 | + if match1(i)<=m && ci(rpi)==match1(i), mi(tripi(rpi))=1; end |
| 235 | + end |
| 236 | + end |
| 237 | +end |
| 238 | + |
| 239 | + |
| 240 | + |
0 commit comments