More compact graphs: do back edges before forward expansion.

This commit is contained in:
Todd Gamblin 2014-12-29 14:28:24 -08:00
parent 226de0a42d
commit daf1e229f7

View file

@ -1330,105 +1330,140 @@ def graph(self, **kwargs):
N = kwargs.get('node', 'o') # Node character N = kwargs.get('node', 'o') # Node character
color = kwargs.get('color', True) color = kwargs.get('color', True)
out = kwargs.get('out', ColorStream(sys.stdout, color=color)) out = kwargs.get('out', ColorStream(sys.stdout, color=color))
debug = kwargs.get('debug', False)
indent = kwargs.get('indent', 0) indent = kwargs.get('indent', 0)
indent *= ' ' indent *= ' '
topo_order = self.topological_sort(reverse=True) topo_order = self.topological_sort(reverse=True)
# Work on a clone so the spec is self contained (no incoming
# parent edges), and so we don't destroy this spec.
clone = self.copy() clone = self.copy()
# Fast access to nodes in the spec.
nodes = clone.index() nodes = clone.index()
frontier = []
def ordered_deps(node): def ordered_deps(node):
deps = node.dependencies deps = node.dependencies
return sorted((d for d in deps), reverse=True) return sorted((d for d in deps), reverse=True)
frontier = []
debug = False def back_edge(end, start, **kwargs):
def back_edge(end, start):
assert(end < start) assert(end < start)
collapse = kwargs.get('collapse', True)
label = kwargs.get('label', '') # debug label
if (start - end) > 1: if (start - end) > 1:
if debug: # This part draws a long back edge.
out.write(" " * 80)
out.write(indent) out.write(indent)
out.write("| " * (end + 1)) out.write("| " * (end + 1))
out.write("|_" * (start - end - 2)) out.write("|_" * (start - end - 2))
out.write("|/ ") out.write("|/")
if collapse:
out.write(" ")
out.write("/ " * (len(frontier) - start)) out.write("/ " * (len(frontier) - start))
out.write("\n") else:
out.write("| " * (len(frontier) - start))
if debug: if debug:
out.write(" " * 80) out.write(" " * 20)
out.write("%s %s" % (frontier, label))
out.write("\n")
# This part draws the final collapsing line
out.write(indent) out.write(indent)
out.write("| " * end) out.write("| " * end)
out.write("|/") out.write("|/")
out.write("| " * (start - end - 1)) out.write("| " * (start - end - 1))
if (start - end) > 1: if (start - end) > 1 or not collapse:
out.write("| " * (len(frontier) - start)) out.write("| " * (len(frontier) - start))
else: else:
out.write(" /" * (len(frontier) - start)) out.write(" /" * (len(frontier) - start))
if debug:
out.write(" " * 20)
out.write("%s %s" % (frontier, label))
out.write("\n") out.write("\n")
def connect_deps(i, deps): def connect_deps(i, deps, **kwargs):
if len(deps) == 1 and deps in frontier: if len(deps) == 1 and deps in frontier:
j = frontier.index(deps) j = frontier.index(deps)
if j < i: if j < i:
back_edge(j, i) back_edge(j, i, **kwargs)
else: else:
if i < j: if i < j:
frontier.pop(j) frontier.pop(j)
frontier.insert(i, deps) frontier.insert(i, deps)
back_edge(i, j+1) back_edge(i, j+1, **kwargs)
return True
elif deps: elif deps:
frontier.insert(i, deps) frontier.insert(i, deps)
return False
def add_deps_to_frontier(node, i): def add_deps_to_frontier(node, i):
deps = ordered_deps(node) deps = ordered_deps(node)
connect_deps(i, deps) connect_deps(i, deps, label="add_deps")
for d in deps: for d in deps:
del nodes[d].dependents[node.name] del nodes[d].dependents[node.name]
name = topo_order.pop() def find(seq, predicate):
add_deps_to_frontier(nodes[name], 0) for i, elt in enumerate(seq):
if predicate(elt):
return i
return -1
if debug:
out.write("%-80s" % frontier)
add_deps_to_frontier(self, 0)
out.write(indent) out.write(indent)
out.write('%s %s\n' % (N, name)) out.write('%s %s\n' % (N, self.name))
topo_order.pop()
while topo_order: while frontier:
if debug: # Find an unexpanded part of frontier
out.write("%-80s" % frontier) i = find(frontier, lambda f: len(f) > 1)
# Find last i, len(frontier[i]) > 1
i = len(frontier) - 1
for f in frontier[-1::-1]:
if len(f) > 1: break
i -= 1
# Expand frontier until there are enough columns for all children. # Expand frontier until there are enough columns for all children.
if i >= 0: if i >= 0:
# Do all back connections possible from this element
# before expanding.
back_connect = [d for d in frontier[i] if [d] in frontier[:i]]
for d in back_connect:
j = frontier.index([d])
frontier[i].remove(d)
connect_deps(i, [d], collapse=False, label="back_connect")
if not frontier[i]:
frontier.pop(i)
elif len(frontier[i]) > 1:
name = frontier[i].pop(0)
deps = [name]
out.write(indent) out.write(indent)
out.write("| " * i) out.write("| " * i)
out.write("|\ ") out.write("|\ ")
out.write("\ " * (len(frontier) - i - 1)) out.write("\ " * (len(frontier) - i - 1))
out.write("\n") out.write("\n")
name = frontier[i].pop(0) connect_deps(i, deps, label="expansion")
deps = [name]
connect_deps(i, deps) # Handle any back edges to the right
if i+1 < len(frontier) and len(frontier[i+1]) == 1: j = i+1
deps = frontier.pop(i+1) while j < len(frontier):
connect_deps(i+1, deps) deps = frontier.pop(j)
# TODO: semantics of connect_deps are weird.
# TODO: false return means the popped item was put
# TODO: back & not connected.
if not connect_deps(j, deps, label="ending"):
j += 1
else: else:
name = topo_order.pop() name = topo_order.pop()
@ -1438,9 +1473,10 @@ def add_deps_to_frontier(node, i):
# the frontier. Since specs are single-rooted DAGs, # the frontier. Since specs are single-rooted DAGs,
# the node is always there. If the graph had multiple # the node is always there. If the graph had multiple
# roots, we'd need to handle that case case of a new root. # roots, we'd need to handle that case case of a new root.
i, elt = next(f for f in enumerate(frontier) if name in f[1]) i = find(frontier, lambda f: name in f)
frontier.pop(i) frontier.pop(i)
out.write(indent)
out.write("| " * i) out.write("| " * i)
out.write("%s " % N) out.write("%s " % N)
out.write("| " * (len(frontier) - i)) out.write("| " * (len(frontier) - i))
@ -1449,9 +1485,7 @@ def add_deps_to_frontier(node, i):
if node.dependencies: if node.dependencies:
add_deps_to_frontier(node, i) add_deps_to_frontier(node, i)
elif frontier: elif frontier:
if debug: out.write(indent)
out.write(" " * 80)
out.write("| " * i) out.write("| " * i)
out.write(" /" * (len(frontier) - i)) out.write(" /" * (len(frontier) - i))
out.write("\n") out.write("\n")