Spaces:
Runtime error
Runtime error
| import collections | |
| import sys | |
| def make_clique(graph, nodes): | |
| for v1 in nodes: | |
| for v2 in nodes: | |
| if v1 != v2: | |
| graph[v1].add(v2) | |
| def count_fillin(graph, nodes): | |
| """How many edges would be needed to make v a clique.""" | |
| count = 0 | |
| for v1 in nodes: | |
| for v2 in nodes: | |
| if v1 != v2 and v2 not in graph[v1]: | |
| count += 1 | |
| return count/2 | |
| def is_clique(graph, vs): | |
| for v1 in vs: | |
| for v2 in vs: | |
| if v1 != v2 and v2 not in graph[v1]: | |
| return False | |
| return True | |
| def simplicial(graph, v): | |
| return is_clique(graph, graph[v]) | |
| def almost_simplicial(graph, v): | |
| for u in graph[v]: | |
| if is_clique(graph, graph[v] - {u}): | |
| return True | |
| return False | |
| def eliminate_node(graph, v): | |
| make_clique(graph, graph[v]) | |
| delete_node(graph, v) | |
| def delete_node(graph, v): | |
| for u in graph[v]: | |
| graph[u].remove(v) | |
| del graph[v] | |
| def contract_edge(graph, u, v): | |
| """Contract edge (u,v) by removing u""" | |
| graph[v] = (graph[v] | graph[u]) - {u, v} | |
| del graph[u] | |
| for w in graph: | |
| if u in graph[w]: | |
| graph[w] = (graph[w] | {v}) - {u, w} | |
| def copy_graph(graph): | |
| return {u:set(graph[u]) for u in graph} | |
| def upper_bound(graph): | |
| """Min-fill.""" | |
| graph = copy_graph(graph) | |
| dmax = 0 | |
| order = [] | |
| while len(graph) > 0: | |
| #d, u = min((len(graph[u]), u) for u in graph) # min-width | |
| d, u = min((count_fillin(graph, graph[u]), u) for u in graph) | |
| dmax = max(dmax, len(graph[u])) | |
| eliminate_node(graph, u) | |
| order.append(u) | |
| return dmax, order | |
| def lower_bound(graph): | |
| """Minor-min-width""" | |
| graph = copy_graph(graph) | |
| dmax = 0 | |
| while len(graph) > 0: | |
| # pick node of minimum degree | |
| d, u = min((len(graph[u]), u) for u in graph) | |
| dmax = max(dmax, d) | |
| # Gogate and Dechter: minor-min-width | |
| nb = graph[u] - {u} | |
| if len(nb) > 0: | |
| _, v = min((len(graph[v] & nb), v) for v in nb) | |
| contract_edge(graph, u, v) | |
| else: | |
| delete_node(graph, u) | |
| return dmax | |
| class Solution(object): | |
| pass | |
| def quickbb(graph): | |
| """Gogate and Dechter, A complete anytime algorithm for treewidth. UAI | |
| 2004. http://arxiv.org/pdf/1207.4109.pdf""" | |
| """Given a permutation of the nodes (called an elimination ordering), | |
| for each node, remove the node and make its neighbors into a clique. | |
| The maximum degree of the nodes at the time of their elimination is | |
| the width of the tree decomposition corresponding to that ordering. | |
| The treewidth of the graph is the minimum over all possible | |
| permutations. | |
| """ | |
| best = Solution() # this gets around the lack of nonlocal in Python 2 | |
| best.count = 0 | |
| def bb(graph, order, f, g): | |
| best.count += 1 | |
| if len(graph) < 2: | |
| if f < best.ub: | |
| assert f == g | |
| best.ub = f | |
| best.order = list(order) + list(graph) | |
| else: | |
| vs = [] | |
| for v in graph: | |
| # very important pruning rule | |
| if simplicial(graph, v) or almost_simplicial(graph, v) and len(graph[v]) <= lb: | |
| vs = [v] | |
| break | |
| else: | |
| vs.append(v) | |
| for v in vs: | |
| graph1 = copy_graph(graph) | |
| eliminate_node(graph1, v) | |
| order1 = order + [v] | |
| # treewidth for current order so far | |
| g1 = max(g, len(graph[v])) | |
| # lower bound given where we are | |
| f1 = max(g, lower_bound(graph1)) | |
| if f1 < best.ub: | |
| bb(graph1, order1, f1, g1) | |
| graph = { u : set(graph[u]) for u in graph } | |
| order = [] | |
| best.ub, best.order = upper_bound(graph) | |
| lb = lower_bound(graph) | |
| if lb < best.ub: | |
| bb(graph, order, lb, 0) | |
| # Build the tree decomposition | |
| tree = collections.defaultdict(set) | |
| def build(order): | |
| if len(order) < 2: | |
| bag = frozenset(order) | |
| tree[bag] = set() | |
| return | |
| v = order[0] | |
| clique = graph[v] | |
| eliminate_node(graph, v) | |
| build(order[1:]) | |
| for tv in tree: | |
| if clique.issubset(tv): | |
| break | |
| bag = frozenset(clique | {v}) | |
| tree[bag].add(tv) | |
| tree[tv].add(bag) | |
| build(best.order) | |
| return tree | |
| if True and __name__ == "__main__": | |
| import fileinput, sys | |
| import graph | |
| s = [] | |
| for line in fileinput.input(): | |
| if line.lstrip().startswith('#'): | |
| continue | |
| s.append(line) | |
| s = ''.join(s) | |
| i = 0 | |
| while i < len(s): | |
| try: | |
| g, i1 = graph.scan_graph(s, start=i, return_end=True) | |
| except: | |
| sys.stderr.write("couldn't read: %s\n" % s[i:i1]) | |
| if g is None: break | |
| i = i1 | |
| g = g.undirected_graph() | |
| tree = quickbb(g) | |
| print(max(len(tv)-1 for tv in tree)) | |
| #print tree | |
| if False and __name__ == "__main__": | |
| import fileinput, sys | |
| g = collections.defaultdict(set) | |
| for line in fileinput.input(): | |
| if line.rstrip() == "END": | |
| break | |
| u, v = line.split() | |
| g[u].add(v) | |
| g[v].add(u) | |
| tree = quickbb(g) | |
| root = list(tree)[0] | |
| def visit(tu, indent, memo): | |
| if tu in memo: return | |
| memo.add(tu) | |
| print(" "*indent, " ".join(tu)) | |
| for tv in tree[tu]: | |
| visit(tv, indent+2, memo) | |
| visit(root, 0, set()) | |
| print("bags:", len(tree)) | |
| print("width:", max(len(tv)-1 for tv in tree)) | |