def find_perfect_matching(graph, match=None): ''' find perfect matching with max cardinality on unweighted undirected graph :param graph: UGraph object, not compatible with weighted UGraph :param match: UGraph object, default None at the beginning :returns: UGraph object, self.edge attribute give the mathcing edge tuple list ''' if match is None: match = UGraph([]) path = find_aug_path(graph, match) if not path: return match else: new_edges = match.edge logger.info('find augment path as %s'%path) for i in range(len(path) - 1): if i % 2 == 0: new_edges.append((path[i].name, path[i + 1].name)) else: if path[i].name < path[i+1].name: new_edges.remove((path[i].name, path[i+1].name)) else: new_edges.remove((path[i+1].name, path[i].name)) match = UGraph(new_edges) return find_perfect_matching(graph, match)
def __init__(self, graph): self.graph = graph self.n = len(graph.vertex) # number of vertex of original graph if self.n == 0: logger.warning('Empty graph') maxweight = 0 else: maxweight = max(graph.rep, key=lambda x: x[2])[2] self.m = len(graph.edge) # number of edge of original graph self.edge = graph.edge self.weight = {(e[0], e[1]): e[2] for e in graph.rep} # edge weight correspondence self.z = {i: maxweight / 2 for i in graph.vertex } # valid for all vertex v and blossom in any level # blossom is indexed by the tuple rep, which is stored in self.status self.status = {i: [(i, )] for i in graph.vertex} self.contain = { i: [i] for i in graph.vertex } # valid for top v, for v not in top level, its elements are for diff levels # by status together with contain, we can know the over all structure of the nested blossoms # status: {1: [(1,),(1,2),(3,1)], 2: [(2,),(1,2),(3,1)], 3:[(3,),(3,1)]}, each item of dict track the blossoms from bottom to top # contain: {1:[1,2,3,4], 2:[3,4]...} each item of dict track all basic vertex contained in some high level effective vertex self.match = UGraph([]) # relative matching on top level self.rgraph = UGraph(self.graph.edge) # reduced graph # quantities below clear every phase self.stf = {i: 'F' for i in graph.vertex } # valid for all real v, no matter level it is self.stfb = { i: None for i in graph.vertex } # valid for top blossom, labeled as v, None stands no blossom in v self.parent = {i: None for i in graph.vertex } # valid for top level, b labeled as v self.mark = {i: False for i in graph.edge } # defined on edge of top level, relative mark self.checkq = [] # check queue self.static_status = None # desinged for keep blossom info for the final all-expansion
def graph_update( self): # reconstruct the rgraph based on current self.status graph_edges = set() for e in self.graph.edge: newe = self.edge_tonew(e) if newe is not None: graph_edges.add(newe) self.rgraph = UGraph(list(graph_edges))
def alt_path(self, path): logger.info('the augmented path to be alternated: %s' % path) new_edges = self.match.edge for i in range(len(path) - 1): if i % 2 == 0: new_edges.append((path[i], path[i + 1])) else: new_edges.remove(edge_reg((path[i], path[i + 1]))) self.match = UGraph(new_edges)
def find_maximum_matching(graph_edges, debug=False): ''' find match with maximum total weights on undirected weighted graph :param graph_edges: edge list in the form : [(2,3,6),(5,4,3),(4,2,7)], in each tuple, the first two elements are edge end vertex, and the last term is the weight of corresponding edge :param debug: boolean, default False, if set as True, the results would be check based on the theorem :returns: UGraph object, self.edge attribute give the mathcing edge tuple list ''' st = DressedGraph(UGraph(graph_edges)) st.phases() if debug is True: st.check_optimum() return st.match
def find_perfect_maximum_matching(graph_edges): ''' find perfect matching with maximum total weights on undirected weighted graph :param graph_edges: edge list in the form : [(2,3,6),(5,4,3),(4,2,7)], in each tuple, the first two elements are edge end vertex, and the last term is the weight of corresponding edge :param debug: boolean, default False, if set as True, the results would be check based on the theorem :returns: UGraph object, self.edge attribute give the mathcing edge tuple list ''' weights_sum = sum(e[2] for e in graph_edges) reweight_graph = [] for e in graph_edges: reweight_graph.append((e[0], e[1], e[2] + weights_sum)) st = DressedGraph(UGraph(reweight_graph)) st.phases() return st.match
def reduce_graph(graph, btuple): new_edges = [] blossom_ele = list(btuple) for e in graph.edge: ee = list(e) if (e[0] in blossom_ele) and (e[1] not in blossom_ele): ee[0] = btuple[0] if edge_reg(tuple(ee)) not in new_edges: new_edges.append(edge_reg(tuple(ee))) elif (e[1] in blossom_ele) and (e[0] not in blossom_ele): ee[1] = btuple[0] if edge_reg(tuple(ee)) not in new_edges: new_edges.append(edge_reg(tuple(ee))) elif (e[1] in blossom_ele) and (e[0] in blossom_ele): pass else: if e not in new_edges: new_edges.append(e) return UGraph(new_edges)
def reduce_graph(graph, blossom): new_edges = [] blossom_ele = [m.name for m in blossom.loop] for e in graph.edge: ee = list(e) if (e[0] in blossom_ele) and (e[1] not in blossom_ele): ee[0] = blossom.base.name if tuple(ee) not in new_edges: new_edges.append(tuple(ee)) elif (e[1] in blossom_ele) and (e[0] not in blossom_ele): ee[1] = blossom.base.name if tuple(ee) not in new_edges: new_edges.append(tuple(ee)) elif (e[1] in blossom_ele) and (e[0] in blossom_ele): pass else: if e not in new_edges: new_edges.append(e) logger.debug('new edges list in the reduced graph or match: %s' % new_edges) graphr = UGraph(new_edges) return graphr
def expand_blossom(self, vrep, delz=True): # v is the rep of the blossom at top level # delz is False for final expand-all if self.blossom_level(vrep) == 0: # compatible with vertex expansion return None btuple = self.status[vrep][-1] logger.debug('expand blossom with vertex contained: %s' % list(btuple)) # check whether there is a child attached to this blossom child = None for v in self.top_vertice(): if vrep == self.parent[v]: child = v break logger.debug('z: %s' % self.z) # # logger.debug('the status:%s'% self.status) if delz is True: del self.z[btuple] for v in self.contain[btuple[0]]: self.status[v].pop(-1) for v in btuple[1:]: for u in self.contain[v]: self.contain[vrep].remove(u) logger.debug('the status:%s' % self.status) logger.debug('the contain:%s' % self.contain) self.stfb[vrep] = None self.graph_update() # expand the graph # update parent and stf(b) father = self.parent[vrep] for u in btuple: self.parent[u] = None self.stf_set(u, 'F') if father is not None: for ve in self.rgraph.adj[father]: if ve in btuple and self.is_equal(edge_reg( (ve, father))): # or (delz is False) start = ve break if child is None: self.parent[start] = father self.stf_set(start, 'T') else: for ve in self.rgraph.adj[child]: if ve in btuple and self.is_equal(edge_reg( (ve, child))): #or (delz is False) end = ve break self.parent[child] = end # sid = btuple.index(start) eid = btuple.index(end) blist = list(btuple) blist = list_shift(blist, len(blist) - eid) # eid at index 0 # logger.debug('the blist: %s' % blist) # sid = blist.index(start) eid = blist.index(end) if (eid - sid) % 2 == 1: blist.reverse() blist = list_shift(blist, 1) # logger.debug('the blist: %s' % blist) # logger.debug('the start, end, and the blist: %s,%s,%s' % (start, end, blist)) # for j, ve in enumerate(blist): if ve == start: self.parent[ve] = father logger.debug('set %s parent as %s' % (ve, father)) self.stf_set(ve, 'T') break else: self.parent[ve] = blist[j + 1] logger.debug('set %s parent as %s' % (ve, blist[j + 1])) if j % 2 == 0: self.stf_set(ve, 'T') else: self.stf_set(ve, 'S') logger.debug('outermost stf of blossoms: %s' % self.stfb) logger.debug('parent state after blossom exposion: %s' % self.parent) # update mark new_mark = {} for e in self.rgraph.edge: if e in self.mark and (e[0] not in btuple) and (e[1] not in btuple): new_mark[e] = self.mark[e] else: if (self.parent[e[0]] == e[1]) or (self.parent[e[1]] == e[0]): new_mark[e] = True else: new_mark[e] = False self.mark = new_mark # logger.debug('mark state after blossom expansion: %s'%self.mark) new_edges = self.match.edge # update relative match # check is there is a match line: blist = list(btuple) if vrep in self.match.vertex: outer = self.match.adj[vrep][0] for member in btuple: #logger.debug('member %s, outer %s, in edges %s, equal %s'%(member, outer, edge_reg((member, outer)) in self.rgraph.edge # ,self.is_equal(edge_reg((member, outer)))))# if (edge_reg((member, outer)) in self.rgraph.edge): # if delz is False: # logger.debug('member %s, outer %s, equal %s, blossom included %s' # %(member, outer,self.is_equal(edge_reg((member, outer))), self.blossoms_cover_edge(edge_reg((member, outer))) ))# if ( self.is_equal(edge_reg((member, outer))) ): #or (not delz): # delz is in last phase, there is nonzero z # blossom to be expanded and hence is_euqal doesn't work inner = member if self.parent[outer] == member: break # logger.debug('remove edge (%s,%s) from new match'%(vrep, outer)) new_edges.remove(edge_reg((vrep, outer))) # logger.debug('add edge (%s,%s) into new match' % (inner, outer)) new_edges.append(edge_reg((inner, outer))) # choose match edges within blossom based on matched (inner,outer) edge rem = btuple.index(inner) # logger.debug('inner point and its index is: %s, %s' %(inner, rem)) blist = list_shift(blist, len(btuple) - rem) # logger.debug('blossom loop from base point: %s'%blist) for i, _ in enumerate(blist): if i % 2 == 1: new_edges.append(edge_reg((blist[i], blist[i + 1]))) self.match = UGraph(new_edges) logger.debug('ending of expansion blossom: %s' % list(btuple))