Skip to content

Commit a3a6dfb

Browse files
authored
Merge pull request #23 from eggrobin/colouring
Better graph colouring
2 parents f203d75 + 44e93e5 commit a3a6dfb

File tree

1 file changed

+81
-17
lines changed

1 file changed

+81
-17
lines changed

Diff for: osaca/semantics/kernel_dg.py

+81-17
Original file line numberDiff line numberDiff line change
@@ -523,8 +523,10 @@ def export_graph(self, filepath=None):
523523
for dep in lcd:
524524
lcd_line_numbers[dep] = [x.line_number for x, lat in lcd[dep]["dependencies"]]
525525
# add color scheme
526-
graph.graph["node"] = {"colorscheme": "set312"}
527-
graph.graph["edge"] = {"colorscheme": "set312"}
526+
graph.graph["node"] = {"colorscheme": "spectral9"}
527+
graph.graph["edge"] = {"colorscheme": "spectral9"}
528+
min_color = 2
529+
available_colors = 8
528530

529531
# create LCD edges
530532
for dep in lcd_line_numbers:
@@ -543,21 +545,14 @@ def export_graph(self, filepath=None):
543545
for n in cp:
544546
graph.nodes[n.line_number]["instruction_form"].latency_cp = n.latency_cp
545547

546-
# color CP and LCD
548+
# Make the critical path bold.
547549
for n in graph.nodes:
548550
if n in cp_line_numbers:
549551
# graph.nodes[n]['color'] = 1
550552
graph.nodes[n]["style"] = "bold"
551553
graph.nodes[n]["penwidth"] = 4
552-
for col, dep in enumerate(lcd):
553-
if n in lcd_line_numbers[dep]:
554-
if "style" not in graph.nodes[n]:
555-
graph.nodes[n]["style"] = "filled"
556-
elif ",filled" not in graph.nodes[n]["style"]:
557-
graph.nodes[n]["style"] += ",filled"
558-
graph.nodes[n]["fillcolor"] = 2 + col % 11
559554

560-
# color edges
555+
# Make critical path edges bold.
561556
for e in graph.edges:
562557
if (
563558
graph.nodes[e[0]]["instruction_form"].line_number in cp_line_numbers
@@ -571,12 +566,81 @@ def export_graph(self, filepath=None):
571566
if bold_edge:
572567
graph.edges[e]["style"] = "bold"
573568
graph.edges[e]["penwidth"] = 3
574-
for dep in lcd_line_numbers:
575-
if (
576-
graph.nodes[e[0]]["instruction_form"].line_number in lcd_line_numbers[dep]
577-
and graph.nodes[e[1]]["instruction_form"].line_number in lcd_line_numbers[dep]
578-
):
579-
graph.edges[e]["color"] = graph.nodes[e[1]]["fillcolor"]
569+
570+
# Color the cycles created by loop-carried dependencies, longest first, never recoloring
571+
# any node, so that the longest LCD and most long chains that are involved in the loop are
572+
# legible.
573+
for i, dep in enumerate(sorted(lcd, key=lambda dep: -lcd[dep]["latency"])):
574+
# For cycles that are broken by already-colored (longer) cycles, the color need not be
575+
# the same for each yet-uncolored arc.
576+
# Do not use the same color for such an arc as for the cycles that delimit it. This is
577+
# always possible with 3 colors, as each arc is only adjacent to the preceding and
578+
# following interrupting cycles.
579+
# Since we color edges as well as nodes, there would be room for a more interesting
580+
# graph coloring problem: we could avoid having unrelated arcs with the same color
581+
# meeting at the same vertex, and retain the same color between arcs of the same cycle
582+
# that are interrupted by a single vertex. We mostly ignore this problem.
583+
584+
# The longest cycle will always have color 1, the second longest cycle will always have
585+
# color 2 except where it overlaps with with the longest cycle, etc.; for arcs that are
586+
# part of short cycles, the colors will be less predictable.
587+
default_color = min_color + i % available_colors
588+
arc = []
589+
arc_source = lcd_line_numbers[dep][-1]
590+
arcs = []
591+
for n in lcd_line_numbers[dep]:
592+
if "fillcolor" in graph.nodes[n]:
593+
arcs.append((arc, (arc_source, n)))
594+
arc = []
595+
arc_source = n
596+
else:
597+
arc.append(n)
598+
if not arcs: # Unconstrained cycle.
599+
arcs.append((arc, tuple()))
600+
else:
601+
arcs.append((arc, (arc_source, lcd_line_numbers[dep][0])))
602+
# Try to color the whole cycle with its default color, then with a single color, then
603+
# with different colors by arc, preferring the default.
604+
forbidden_colors = set(
605+
graph.nodes[n]["fillcolor"] for arc, extremities in arcs for n in extremities
606+
if "fillcolor" in graph.nodes[n]
607+
)
608+
global_color = None
609+
if default_color not in forbidden_colors:
610+
global_color = default_color
611+
elif len(forbidden_colors) < available_colors:
612+
global_color = next(
613+
c for c in range(min_color, min_color + available_colors + 1)
614+
if c not in forbidden_colors
615+
)
616+
for arc, extremities in arcs:
617+
if global_color:
618+
color = global_color
619+
else:
620+
color = default_color
621+
while color in (graph.nodes[n].get("fillcolor") for n in extremities):
622+
color = min_color + (color + 1) % available_colors
623+
for n in arc:
624+
if "style" not in graph.nodes[n]:
625+
graph.nodes[n]["style"] = "filled"
626+
else:
627+
graph.nodes[n]["style"] += ",filled"
628+
graph.nodes[n]["fillcolor"] = color
629+
if extremities:
630+
(source, sink) = extremities
631+
else:
632+
source = sink = arc[0]
633+
arc = arc[1:]
634+
for u, v in zip([source] + arc, arc + [sink]):
635+
# The backward edge of the cycle is represented as the corresponding forward
636+
# edge with the attribute dir=back.
637+
edge = graph.edges[v, u] if (v, u) in graph.edges else graph.edges[u, v]
638+
if arc:
639+
if "color" in edge:
640+
raise AssertionError(
641+
f"Recoloring {u}->{v} in arc ({source}) {arc} ({sink}) of {dep}"
642+
)
643+
edge["color"] = color
580644

581645
# rename node from [idx] to [idx mnemonic] and add shape
582646
mapping = {}

0 commit comments

Comments
 (0)