|
| 1 | +# %% |
| 2 | +# This cell generates a random graph for the topological sort to run on. |
| 3 | + |
| 4 | +%matplotlib widget |
| 5 | +from ogdf_python import ogdf, cppinclude |
| 6 | +import cppyy |
| 7 | + |
| 8 | +cppinclude("ogdf/basic/graph_generators/randomized.h") |
| 9 | +cppinclude("ogdf/layered/SugiyamaLayout.h") |
| 10 | +cppinclude("ogdf/basic/simple_graph_alg.h") |
| 11 | + |
| 12 | +G = ogdf.Graph() |
| 13 | +GA = ogdf.GraphAttributes(G, ogdf.GraphAttributes.all) |
| 14 | +GA.directed = True |
| 15 | + |
| 16 | + |
| 17 | +def make_graph(): |
| 18 | + ogdf.setSeed(1) |
| 19 | + G.clear() |
| 20 | + G2 = ogdf.Graph() |
| 21 | + ogdf.randomPlanarTriconnectedGraph(G2, 10, 20) |
| 22 | + G.insert(G2) |
| 23 | + ogdf.randomPlanarTriconnectedGraph(G2, 5, 10) |
| 24 | + G.insert(G2) |
| 25 | + ogdf.makeAcyclicByReverse(G) |
| 26 | + |
| 27 | + for e in G.edges: |
| 28 | + GA.label[e] = str(e.index()) |
| 29 | + for n in G.nodes: |
| 30 | + GA.label[n] = str(n.index()) |
| 31 | + |
| 32 | + SL = ogdf.SugiyamaLayout() |
| 33 | + SL.call(GA) |
| 34 | + GA.rotateLeft90() |
| 35 | + |
| 36 | + G.delEdge(G.edges[4]) # add a second source |
| 37 | + |
| 38 | + |
| 39 | +make_graph() |
| 40 | +GA |
| 41 | + |
| 42 | +# %% |
| 43 | +good_order = [ |
| 44 | + G.nodes[7], |
| 45 | + G.nodes[13], |
| 46 | + G.nodes[12], |
| 47 | + G.nodes[15], |
| 48 | + G.nodes[5], |
| 49 | + G.nodes[11], |
| 50 | + G.nodes[14], |
| 51 | + G.nodes[6], |
| 52 | + G.nodes[10], |
| 53 | + G.nodes[4], |
| 54 | + G.nodes[8], |
| 55 | + G.nodes[3], |
| 56 | + G.nodes[2], |
| 57 | + G.nodes[1], |
| 58 | + G.nodes[9], |
| 59 | + G.nodes[0], |
| 60 | +] |
| 61 | +bad_order = [ |
| 62 | + G.nodes[7], |
| 63 | + G.nodes[13], |
| 64 | + G.nodes[12], |
| 65 | + G.nodes[15], |
| 66 | + G.nodes[5], |
| 67 | + G.nodes[11], |
| 68 | + G.nodes[14], |
| 69 | + G.nodes[6], |
| 70 | + G.nodes[10], |
| 71 | + G.nodes[3], |
| 72 | + G.nodes[8], |
| 73 | + G.nodes[4], |
| 74 | + G.nodes[2], |
| 75 | + G.nodes[1], |
| 76 | + G.nodes[9], |
| 77 | + G.nodes[0], |
| 78 | +] |
| 79 | + |
| 80 | + |
| 81 | +# %% |
| 82 | +def validate(order): |
| 83 | + seen = set() |
| 84 | + for n in order: |
| 85 | + for adj in n.adjEntries: |
| 86 | + if not adj.isSource(): |
| 87 | + continue |
| 88 | + if not adj.twinNode() in seen: |
| 89 | + print(adj) |
| 90 | + return False |
| 91 | + seen.add(n) |
| 92 | + return True |
| 93 | + |
| 94 | + |
| 95 | +# %% |
| 96 | +validate(good_order) |
| 97 | + |
| 98 | +# %% |
| 99 | +validate(bad_order) |
| 100 | + |
| 101 | +# %% |
| 102 | +degree = ogdf.NodeArray[int](G, 0) # degree after node deletion |
| 103 | +for n in G.nodes: |
| 104 | + degree[n] = n.outdeg() |
| 105 | + if degree[n] == 0: |
| 106 | + GA.fillColor[n] = ogdf.Color("#FCC") |
| 107 | +sinks = [n for n in G.nodes if degree[n] == 0] # sink nodes |
| 108 | + |
| 109 | +order = [] # order of nodes |
| 110 | +index = ogdf.NodeArray[int](G, -1) # position in order |
| 111 | + |
| 112 | +# %% |
| 113 | +import random |
| 114 | + |
| 115 | + |
| 116 | +def step(): |
| 117 | + n = sinks.pop(random.randrange(len(sinks))) |
| 118 | + index[n] = i = len(order) |
| 119 | + order.append(n) |
| 120 | + |
| 121 | + GA.label[n] = f"{i}" |
| 122 | + GA.strokeColor[n] = ogdf.Color("#666") |
| 123 | + GA.fillColor[n] = ogdf.Color("#CCC") |
| 124 | + |
| 125 | + # (virtually) remove all edges to n |
| 126 | + for adj in n.adjEntries: |
| 127 | + GA.strokeColor[adj.theEdge()] = ogdf.Color("#CCC") |
| 128 | + |
| 129 | + if adj.isSource(): |
| 130 | + continue |
| 131 | + # else n is the target of the "removed" edge |
| 132 | + # decrement the outdegree of the source of the edge |
| 133 | + degree[adj.twinNode()] -= 1 |
| 134 | + if degree[adj.twinNode()] == 0: |
| 135 | + sinks.append(adj.twinNode()) |
| 136 | + GA.fillColor[adj.twinNode()] = ogdf.Color("#FCC") |
| 137 | + |
| 138 | + |
| 139 | +# %% |
| 140 | +def dump(): |
| 141 | + print("Order:", *order) |
| 142 | + print("Sinks:", *sinks) |
| 143 | + for n in sinks: |
| 144 | + GA.fillColor[n] = ogdf.Color("#FCC") |
| 145 | + return GA |
| 146 | + |
| 147 | + |
| 148 | +dump() |
| 149 | + |
| 150 | +# %% |
| 151 | +step() |
| 152 | +dump() |
| 153 | + |
| 154 | +# %% |
| 155 | +step() |
| 156 | +dump() |
| 157 | + |
| 158 | +# %% |
| 159 | +step() |
| 160 | +dump() |
| 161 | + |
| 162 | +# %% |
| 163 | +import ipywidgets |
| 164 | +from ogdf_python.matplotlib import MatplotlibGraph |
| 165 | + |
| 166 | +w = MatplotlibGraph(GA) |
| 167 | +w_order = ipywidgets.Label() |
| 168 | +w_sinks = ipywidgets.Label() |
| 169 | +b_step = ipywidgets.Button(description="Step") |
| 170 | +b_reset = ipywidgets.Button(description="Reset") |
| 171 | + |
| 172 | + |
| 173 | +def update(): |
| 174 | + w_order.value = "Order: " + ", ".join(str(n.index()) for n in order) |
| 175 | + w_sinks.value = "Sinks: " + ", ".join(str(n.index()) for n in sinks) |
| 176 | + b_step.disabled = not sinks |
| 177 | + |
| 178 | + |
| 179 | +def b_step_click(*args): |
| 180 | + step() |
| 181 | + update() |
| 182 | + w.update_node(order[-1]) |
| 183 | + for adj in order[-1].adjEntries: |
| 184 | + w.update_edge(adj.theEdge()) |
| 185 | + for s in sinks: |
| 186 | + w.update_node(s) |
| 187 | + |
| 188 | + |
| 189 | +b_step.on_click(b_step_click) |
| 190 | + |
| 191 | + |
| 192 | +def b_reset_click(*args): |
| 193 | + make_graph() |
| 194 | + order.clear() |
| 195 | + sinks.clear() |
| 196 | + sinks.extend(n for n in G.nodes if n.outdeg() == 0) |
| 197 | + for n in G.nodes: |
| 198 | + degree[n] = n.outdeg() |
| 199 | + if degree[n] == 0: |
| 200 | + GA.fillColor[n] = ogdf.Color("#FCC") |
| 201 | + update() |
| 202 | + w.update_all() |
| 203 | + |
| 204 | + |
| 205 | +b_reset.on_click(b_reset_click) |
| 206 | + |
| 207 | +update() |
| 208 | +ipywidgets.VBox( |
| 209 | + [ipywidgets.HBox([b_step, b_reset]), w_order, w_sinks, w.ax.figure.canvas] |
| 210 | +) |
| 211 | + |
| 212 | +# %% |
| 213 | +N = list(G.nodes) |
| 214 | +print("[", ", ".join(f"G.nodes[{N.index(n)}]" for n in order), "]") |
| 215 | + |
| 216 | +# %% |
| 217 | +# allow changing the displayed node label |
| 218 | +d_label = ipywidgets.Dropdown( |
| 219 | + options=["Index", "Degree"], |
| 220 | + value="Index", |
| 221 | + description="Node label:", |
| 222 | + disabled=False, |
| 223 | +) |
| 224 | + |
| 225 | + |
| 226 | +def label_changed(change): |
| 227 | + for n in G.nodes: |
| 228 | + if index[n] == -1: |
| 229 | + GA.label[n] = ( |
| 230 | + str(n.index()) if change["new"] == "Index" else f"d{degree[n]}" |
| 231 | + ) |
| 232 | + w.update_all() |
| 233 | + |
| 234 | + |
| 235 | +d_label.observe(label_changed, names="value") |
| 236 | +d_label |
0 commit comments