|
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((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: |
|
|
|
d, u = min((len(graph[u]), u) for u in graph) |
|
dmax = max(dmax, d) |
|
|
|
|
|
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() |
|
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: |
|
|
|
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] |
|
|
|
g1 = max(g, len(graph[v])) |
|
|
|
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) |
|
|
|
|
|
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)) |
|
|
|
|
|
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)) |
|
|