def PartialCubeEdgeLabeling(G): """ Label edges of G by their equivalence classes in a partial cube structure. We follow the algorithm of arxiv:0705.1025, in which a number of equivalence classes equal to the maximum degree of G can be found simultaneously by a single breadth first search, using bitvectors. However, in order to avoid deep recursions (problematic in Python) we use a union-find data structure to keep track of edge identifications discovered so far. That is, we repeatedly contract our initial graph, maintaining as we do the property that G[v][w] points to a union-find set representing edges in the original graph that have been contracted to the single edge v-w. """ # Some simple sanity checks if not isUndirected(G): raise Medium.MediumError("graph is not undirected") L = list(StronglyConnectedComponents(G)) if len(L) != 1: raise Medium.MediumError("graph is not connected") # Set up data structures for algorithm: # - UF: union find data structure representing known edge equivalences # - CG: contracted graph at current stage of algorithm # - LL: limit on number of remaining available labels UF = UnionFind() CG = dict([(v, dict([(w, (v, w)) for w in G[v]])) for v in G]) NL = len(CG) - 1 # Initial sanity check: are there few enough edges? # Needed so that we don't try to use union-find on a dense # graph and incur superquadratic runtimes. n = len(CG) m = sum([len(CG[v]) for v in CG]) if 1 << (m // n) > n: raise Medium.MediumError("graph has too many edges") # Main contraction loop in place of the original algorithm's recursion while len(CG) > 1: if not isBipartite(CG): raise Medium.MediumError("graph is not bipartite") # Find max degree vertex in G, and update label limit deg, root = max([(len(CG[v]), v) for v in CG]) if deg > NL: raise Medium.MediumError("graph has too many equivalence classes") NL -= deg # Set up bitvectors on vertices bitvec = dict([(v, 0) for v in CG]) neighbors = {} i = 0 for neighbor in CG[root]: bitvec[neighbor] = 1 << i neighbors[1 << i] = neighbor i += 1 # Breadth first search to propagate bitvectors to the rest of the graph for LG in BFS.BreadthFirstLevels(CG, root): for v in LG: for w in LG[v]: bitvec[w] |= bitvec[v] # Make graph of labeled edges and union them together labeled = dict([(v, set()) for v in CG]) for v in CG: for w in CG[v]: diff = bitvec[v] ^ bitvec[w] if not diff or bitvec[w] & ~bitvec[v] == 0: continue # zero edge or wrong direction if diff not in neighbors: raise Medium.MediumError("multiply-labeled edge") neighbor = neighbors[diff] UF.union(CG[v][w], CG[root][neighbor]) UF.union(CG[w][v], CG[neighbor][root]) labeled[v].add(w) labeled[w].add(v) # Map vertices to components of labeled-edge graph component = {} compnum = 0 for SCC in StronglyConnectedComponents(labeled): for v in SCC: component[v] = compnum compnum += 1 # generate new compressed subgraph NG = dict([(i, {}) for i in range(compnum)]) for v in CG: for w in CG[v]: if bitvec[v] == bitvec[w]: vi = component[v] wi = component[w] if vi == wi: raise Medium.MediumError( "self-loop in contracted graph") if wi in NG[vi]: UF.union(NG[vi][wi], CG[v][w]) else: NG[vi][wi] = CG[v][w] CG = NG # Here with all edge equivalence classes represented by UF. # Turn them into a labeled graph and return it. return dict([(v, dict([(w, UF[v, w]) for w in G[v]])) for v in G])
def RoutingTable(M): """ Return a dictionary mapping pairs (state1,state2) to tokens, such that the action of the token takes state1 closer to state2. By following successive tokens from this table, we can find a path in the medium that uses each token at most once and involves no token-reverse pairs. We use the O(n^2) time algorithm from arxiv:cs.DS/0206033. This is also a key step of the partial cube recognition algorithm from arxiv:0705.1025 -- as part of that algorithm, if we recognize that the input is not a medium, we raise MediumError. """ G = StateTransitionGraph(M) current = initialState = next(iter(M)) # find list of tokens that lead to the initial state activeTokens = set() for LG in BFS.BreadthFirstLevels(G,initialState): for v in LG: for w in LG[v]: activeTokens.add(G[w][v]) for t in activeTokens: if M.reverse(t) in activeTokens: raise MediumError("shortest path to initial state is not concise") activeTokens = list(activeTokens) inactivated = object() # flag object to mark inactive tokens # rest of data structure: point from states to list and list to states activeForState = {S:-1 for S in M} statesForPos = [[] for i in activeTokens] def scan(S): """Find the next token that is effective for s.""" i = activeForState[S] while True: i += 1 if i >= len(activeTokens): raise MediumError("no active token from %s to %s" %(S,current)) if activeTokens[i] != inactivated and M(S,activeTokens[i]) != S: activeForState[S] = i statesForPos[i].append(S) return # set initial active states for S in M: if S != current: scan(S) # traverse the graph, maintaining active tokens visited = set() routes = {} for prev,current,edgetype in DFS.search(G,initialState): if prev != current and edgetype != DFS.nontree: if edgetype == DFS.reverse: prev,current = current,prev # add token to end of list, point to it from old state activeTokens.append(G[prev][current]) activeForState[prev] = len(activeTokens) - 1 statesForPos.append([prev]) # inactivate reverse token, find new token for its states activeTokens[activeForState[current]] = inactivated for S in statesForPos[activeForState[current]]: if S != current: scan(S) # remember routing table as part of returned results if current not in visited: for S in M: if S != current: routes[S,current] = activeTokens[activeForState[S]] return routes