Skip to content

Commit 287a49f

Browse files
committed
fix C++ tutorial, add solutions
1 parent 94ce072 commit 287a49f

6 files changed

Lines changed: 641 additions & 7 deletions

File tree

docs/build-tutorial-zip.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
3+
rm tutorial.zip
4+
latexmk tutorial
5+
6+
for f in {examples,exercises,tutorial}/*.py; do
7+
jupytext -o "${f%.*}.ipynb" "$f"
8+
done
9+
10+
zip -r tutorial.zip data examples/*.ipynb exercises/*.ipynb tutorial/*.ipynb build-ogdf.sh tutorial.pdf
11+
12+
rm {examples,exercises,tutorial}/*.ipynb
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# %%
2+
%matplotlib widget
3+
from ogdf_python import ogdf, cppinclude
4+
5+
cppinclude("ogdf/basic/graph_generators/randomized.h")
6+
7+
G = ogdf.Graph()
8+
ogdf.setSeed(1)
9+
ogdf.randomTree(G, 15)
10+
root = G.nodes[0]
11+
GA = ogdf.GraphAttributes(G, ogdf.GraphAttributes.all)
12+
13+
G
14+
15+
# %%
16+
post = ogdf.NodeArray[int](G, -1)
17+
depth = ogdf.NodeArray[int](G, -1)
18+
19+
20+
def postorder(n, count=0, level=0):
21+
for adj in n.adjEntries:
22+
if not adj.isSource():
23+
continue
24+
count = postorder(adj.twinNode(), count, level + 1)
25+
26+
depth[n] = level
27+
post[n] = count
28+
return count + 1
29+
30+
31+
postorder(root)
32+
33+
# %%
34+
for n in G.nodes:
35+
GA.y[n] = depth[n] * 50
36+
GA.x[n] = post[n] * 50
37+
GA
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
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

Comments
 (0)