Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/graphs/schnyder.py
4058 views
1
"""
2
Schnyder's Algorithm for straight-line planar embeddings.
3
4
A module for computing the (x,y) coordinates for a straight-line planar
5
embedding of any connected planar graph with at least three vertices. Uses
6
Walter Schnyder's Algorithm.
7
8
AUTHORS:
9
-- Jonathan Bober, Emily Kirkman (2008-02-09): initial version
10
11
REFERENCE:
12
[1] Schnyder, Walter. Embedding Planar Graphs on the Grid.
13
Proc. 1st Annual ACM-SIAM Symposium on Discrete Algorithms,
14
San Francisco (1994), pp. 138-147.
15
"""
16
#*****************************************************************************
17
# Copyright (C) 2008 Jonathan Bober and Emily Kirkman
18
#
19
# Distributed under the terms of the GNU General Public License (GPL)
20
# http://www.gnu.org/licenses/
21
#*****************************************************************************
22
23
from sage.sets.set import Set
24
from all import DiGraph
25
26
def _triangulate(g, comb_emb):
27
"""
28
Helper function to schnyder method for computing coordinates in the plane to
29
plot a planar graph with no edge crossings.
30
31
Given a connected graph g with at least 3 vertices and a planar combinatorial
32
embedding comb_emb of g, modify g in place to form a graph whose faces are
33
all triangles, and return the set of newly created edges.
34
35
The simple way to triangulate a face is to just pick a vertex and draw
36
an edge from that vertex to every other vertex in the face. Think that this
37
might ultimately result in graphs that don't look very nice when we draw them
38
so we have decided on a different strategy. After handling special cases,
39
we add an edge to connect every third vertex on each face. If this edge is
40
a repeat, we keep the first edge of the face and retry the process at the next
41
edge in the face list. (By removing the special cases we guarantee that this
42
method will work on one of these attempts.)
43
44
INPUT:
45
g -- the graph to triangulate
46
comb_emb -- a planar combinatorial embedding of g
47
48
RETURNS:
49
A list of edges that are added to the graph (in place)
50
51
EXAMPLES::
52
53
sage: from sage.graphs.schnyder import _triangulate
54
sage: g = Graph(graphs.CycleGraph(4))
55
sage: g.is_planar(set_embedding=True)
56
True
57
sage: _triangulate(g, g._embedding)
58
[(2, 0), (1, 3)]
59
60
sage: g = graphs.PathGraph(3)
61
sage: g.is_planar(set_embedding=True)
62
True
63
sage: _triangulate(g, g._embedding)
64
[(0, 2)]
65
"""
66
# first make sure that the graph has at least 3 vertices, and that it is connected
67
if g.order() < 3:
68
raise ValueError("A Graph with less than 3 vertices doesn't have any triangulation.")
69
if g.is_connected() == False:
70
raise NotImplementedError("_triangulate() only knows how to handle connected graphs.")
71
72
if g.order() == 3 and len(g.edges()) == 2: # if g is o--o--o
73
vertex_list = g.vertices()
74
if len(g.neighbors(vertex_list[0])) == 2: # figure out which of the vertices already has two neighbors
75
new_edge = (vertex_list[1], vertex_list[2]) # and connect the other two together.
76
elif len(g.neighbors(vertex_list[1])) == 2:
77
new_edge = (vertex_list[0], vertex_list[2])
78
else:
79
new_edge = (vertex_list[0], vertex_list[1])
80
81
g.add_edge(new_edge)
82
return [new_edge]
83
84
# At this point we know that the graph is connected, has at least 3 vertices, and
85
# that it is not the graph o--o--o. This is where the real work starts.
86
87
faces = g.trace_faces(comb_emb) # We start by finding all of the faces of this embedding.
88
89
edges_added = [] # The list of edges that we add to the graph.
90
# This will be returned at the end.
91
92
93
for face in faces:
94
new_face = []
95
if len(face) < 3:
96
raise Exception('Triangulate method created face %s with < 3 edges.'%face)
97
if len(face) == 3:
98
continue # This face is already triangulated
99
elif len(face) == 4: # In this special case just add diagonal edge to square
100
new_face = (face[1][1], face[0][0])
101
if g.has_edge(new_face):
102
new_face = (face[2][1], face[1][0])
103
g.add_edge(new_face)
104
edges_added.append(new_face)
105
else:
106
N = len(face)
107
i = 0
108
while i < N-1:
109
new_edge = (face[i+1][1], face[i][0]) # new_edge is from third vertex in face to first
110
if g.has_edge(new_edge) or new_edge[0] == new_edge[1]: # check for repeats
111
new_face.append(face[i]) # if repeated, keep first edge in face instead
112
if i == N - 2: # if we are two from the end, found a triangle already
113
break
114
i = i + 1
115
continue
116
117
g.add_edge(new_edge)
118
edges_added.append(new_edge)
119
new_face.append((new_edge[1], new_edge[0]))
120
i = i + 2
121
if i != N:
122
new_face.append(face[-1])
123
faces.append(new_face)
124
125
return edges_added
126
127
def _normal_label(g, comb_emb, external_face):
128
"""
129
Helper function to schnyder method for computing coordinates in the plane to
130
plot a planar graph with no edge crossings.
131
132
Constructs a normal labelling of a triangular graph g, given the planar
133
combinatorial embedding of g and a designated external face. Returns labels
134
dictionary. The normal label is constructed by first contracting the graph
135
down to its external face, then expanding the graph back to the original while
136
simultaneously adding angle labels.
137
138
INPUT:
139
g -- the graph to find the normal labeling of (g must be triangulated)
140
comb_emb -- a planar combinatorial embedding of g
141
external_face -- the list of three edges in the external face of g
142
143
RETURNS:
144
x -- tuple with entries
145
x[0] = dict of dicts of normal labeling for each vertex of g and each
146
adjacent neighbors u,v (u < v) of vertex:
147
{ vertex : { (u,v): angel_label } }
148
x[1] = (v1,v2,v3) tuple of the three vertices of the external face.
149
150
EXAMPLES::
151
152
sage: from sage.graphs.schnyder import _triangulate, _normal_label, _realizer
153
sage: g = Graph(graphs.CycleGraph(7))
154
sage: g.is_planar(set_embedding=True)
155
True
156
sage: faces = g.trace_faces(g._embedding)
157
sage: _triangulate(g, g._embedding)
158
[(2, 0), (4, 2), (6, 4), (5, 0), (3, 5), (1, 3), (4, 0), (3, 0)]
159
sage: tn = _normal_label(g, g._embedding, faces[0])
160
sage: _realizer(g, tn)
161
({0: [<sage.graphs.schnyder.TreeNode instance at ...>]},
162
(0, 1, 2))
163
164
"""
165
contracted = []
166
contractible = []
167
168
labels = {}
169
170
external_vertices = [external_face[0][0], external_face[1][0], external_face[2][0]]
171
external_vertices.sort()
172
v1,v2,v3 = external_vertices
173
v1_neighbors = Set(g.neighbors(v1))
174
175
neighbor_count = {}
176
for v in g.vertices():
177
neighbor_count[v] = 0
178
for v in g.neighbors(v1):
179
neighbor_count[v] = len(v1_neighbors.intersection( Set(g.neighbors(v))))
180
181
182
for v in v1_neighbors:
183
if v in [v1,v2,v3]:
184
continue
185
if neighbor_count[v] == 2:
186
contractible.append(v)
187
188
# contraction phase:
189
190
while g.order() > 3:
191
try:
192
v = contractible.pop()
193
except:
194
raise Exception('Contractible list is empty but graph still has %d vertices. (Expected 3.)'%g.order())
195
196
break
197
# going to contract v
198
v_neighbors = Set(g.neighbors(v))
199
contracted.append( (v, v_neighbors, v_neighbors - v1_neighbors - Set([v1])) )
200
g.delete_vertex(v)
201
v1_neighbors -= Set([v])
202
for w in v_neighbors - v1_neighbors - Set([v1]):
203
# adding edge (v1, w)
204
g.add_edge( (v1, w) )
205
if g.order() == 3:
206
break
207
v1_neighbors += v_neighbors - Set([v1])
208
contractible = []
209
for w in g.neighbors(v1):
210
if(len(v1_neighbors.intersection( Set(g.neighbors(w))))) == 2 and w not in [v1, v2, v3]:
211
contractible.append(w)
212
213
214
# expansion phase:
215
216
v1, v2, v3 = g.vertices() #always in sorted order
217
218
labels[v1] = {(v2,v3):1}
219
labels[v2] = {(v1,v3):2}
220
labels[v3] = {(v1,v2):3}
221
222
while len(contracted) > 0:
223
v, new_neighbors, neighbors_to_delete = contracted.pop()
224
# going to add back vertex v
225
labels[v] = {}
226
227
for w in neighbors_to_delete:
228
g.delete_edge((v1,w))
229
230
if len(neighbors_to_delete) == 0:
231
# we are adding v into the face new_neighbors
232
w1, w2, w3 = sorted(new_neighbors)
233
234
labels[v] = {(w1, w2): labels[w3].pop((w1,w2)), (w2,w3) : labels[w1].pop((w2,w3)), (w1,w3) : labels[w2].pop((w1,w3))}
235
labels[w1][tuple(sorted((w2,v)))] = labels[v][(w2,w3)]
236
labels[w1][tuple(sorted((w3,v)))] = labels[v][(w2,w3)]
237
238
labels[w2][tuple(sorted((w1,v)))] = labels[v][(w1,w3)]
239
labels[w2][tuple(sorted((w3,v)))] = labels[v][(w1,w3)]
240
241
labels[w3][tuple(sorted((w1,v)))] = labels[v][(w1,w2)]
242
labels[w3][tuple(sorted((w2,v)))] = labels[v][(w1,w2)]
243
else:
244
new_neighbors_set = Set(new_neighbors)
245
angles_out_of_v1 = set()
246
vertices_in_order = []
247
l = []
248
for angle in labels[v1].keys():
249
if len(Set(angle).intersection(new_neighbors_set)) == 2:
250
angles_out_of_v1.add(angle)
251
l = l + list(angle)
252
# find a unique element in l
253
l.sort()
254
i = 0
255
while i < len(l):
256
if l[i] == l[i+1]:
257
i = i + 2
258
else:
259
break
260
261
angle_set = Set(angles_out_of_v1)
262
263
vertices_in_order.append(l[i])
264
while len(angles_out_of_v1) > 0:
265
for angle in angles_out_of_v1:
266
if vertices_in_order[-1] in angle:
267
break
268
if angle[0] == vertices_in_order[-1]:
269
vertices_in_order.append(angle[1])
270
else:
271
vertices_in_order.append(angle[0])
272
angles_out_of_v1.remove(angle)
273
274
w = vertices_in_order
275
276
# is w[0] a 2 or a 3?
277
top_label = labels[w[0]][tuple(sorted((v1, w[1])))]
278
if top_label == 3:
279
bottom_label = 2
280
else:
281
bottom_label = 3
282
i = 0
283
while i < len(w) - 1:
284
labels[v][ tuple(sorted((w[i],w[i+1]))) ] = 1
285
labels[w[i]][ tuple(sorted( (w[i+1], v) )) ] = top_label
286
labels[w[i+1]][ tuple(sorted( (w[i], v) )) ] = bottom_label
287
i = i + 1
288
289
labels[v][tuple(sorted( (v1, w[0])))] = bottom_label
290
labels[v][tuple(sorted( (v1, w[-1])))] = top_label
291
292
293
labels[w[0]][tuple(sorted((v1,v)))] = top_label
294
labels[w[-1]][tuple(sorted((v1,v)))] = bottom_label
295
labels[v1][tuple(sorted( (w[0],v) ))] = 1
296
labels[v1][tuple(sorted( (w[-1],v) ))] = 1
297
298
#delete all the extra labels
299
300
for angle in angle_set:
301
labels[v1].pop( angle )
302
303
labels[w[0]].pop( tuple (sorted( (v1, w[1]) ) ))
304
labels[w[-1]].pop( tuple (sorted( (v1, w[-2]) ) ))
305
306
i = 1
307
while i < len(w) - 1:
308
labels[w[i]].pop(tuple(sorted( (v1, w[i+1]))))
309
labels[w[i]].pop(tuple(sorted( (v1, w[i-1]))))
310
i = i + 1
311
312
for w in new_neighbors:
313
g.add_edge((v,w))
314
315
return labels, (v1,v2,v3)
316
317
def _realizer(g, x, example=False):
318
"""
319
Given a triangulated graph g and a normal labeling constructs the
320
realizer and returns a dictionary of three trees determined by the
321
realizer, each spanning all interior vertices and rooted at one of
322
the three external vertices.
323
324
A realizer is a directed graph with edge labels that span all interior
325
vertices from each external vertex. It is determined by giving direction
326
to the edges that have the same angle label on both sides at a vertex.
327
(Thus the direction actually points to the parent in the tree.) The
328
edge label is set as whatever the matching angle label is. Then from
329
any interior vertex, following the directed edges by label will
330
give a path to each of the three external vertices.
331
332
INPUT:
333
g -- the graph to compute the realizer of
334
x -- tuple with entries
335
x[0] = dict of dicts representing a normal labeling of g. For
336
each vertex of g and each adjacent neighbors u,v (u < v) of
337
vertex: { vertex : { (u,v): angle_label } }
338
x[1] = (v1, v2, v3) tuple of the three external vertices (also
339
the roots of each tree)
340
341
RETURNS:
342
x -- tuple with entries
343
x[0] = dict of lists of TreeNodes:
344
{ root_vertex : [ list of all TreeNodes under root_vertex ] }
345
x[0] = (v1,v2,v3) tuple of the three external vertices (also the
346
roots of each tree)
347
348
EXAMPLES::
349
350
sage: from sage.graphs.schnyder import _triangulate, _normal_label, _realizer
351
sage: g = Graph(graphs.CycleGraph(7))
352
sage: g.is_planar(set_embedding=True)
353
True
354
sage: faces = g.trace_faces(g._embedding)
355
sage: _triangulate(g, g._embedding)
356
[(2, 0), (4, 2), (6, 4), (5, 0), (3, 5), (1, 3), (4, 0), (3, 0)]
357
sage: tn = _normal_label(g, g._embedding, faces[0])
358
sage: _realizer(g, tn)
359
({0: [<sage.graphs.schnyder.TreeNode instance at ...>]},
360
(0, 1, 2))
361
362
"""
363
normal_labeling, (v1, v2, v3) = x
364
realizer = DiGraph()
365
366
tree_nodes = {}
367
for v in g:
368
tree_nodes[v] = [TreeNode(label = v, children = []), TreeNode(label = v, children = []), TreeNode(label = v, children = [])]
369
370
371
for v in g:
372
ones = []
373
twos = []
374
threes = []
375
l = [ones,twos,threes]
376
for angle, value in normal_labeling[v].items():
377
l[value - 1] += list(angle)
378
379
ones.sort()
380
twos.sort()
381
threes.sort()
382
383
i = 0
384
while i < len(ones) - 1:
385
if ones[i] == ones[i+1]:
386
realizer.add_edge( (ones[i], v), label=1)
387
tree_nodes[v][0].append_child(tree_nodes[ones[i]][0])
388
i = i + 1
389
i = i + 1
390
i = 0
391
while i < len(twos) - 1:
392
if twos[i] == twos[i+1]:
393
realizer.add_edge( (twos[i], v), label=2)
394
tree_nodes[v][1].append_child(tree_nodes[twos[i]][1])
395
i = i + 1
396
i = i + 1
397
i = 0
398
while i < len(threes) - 1:
399
if threes[i] == threes[i+1]:
400
realizer.add_edge( (threes[i], v), label=3)
401
tree_nodes[v][2].append_child(tree_nodes[threes[i]][2])
402
i = i + 1
403
i = i + 1
404
405
_compute_coordinates(realizer, (tree_nodes, (v1, v2, v3)))
406
407
if example:
408
realizer.show(talk=True, edge_labels=True)
409
410
return tree_nodes, (v1,v2,v3)
411
412
def _compute_coordinates(g, x):
413
"""
414
Given a triangulated graph g with a dict of trees given by the
415
realizer and tuple of the external vertices, we compute the
416
coordinates of a planar geometric embedding in the grid.
417
418
The coordinates will be set to the _pos attribute of g.
419
420
INPUT:
421
g -- the graph to compute the coordinates of
422
x -- tuple with entries
423
x[0] = dict of tree nodes for the three trees with each external
424
vertex as root
425
{ root_vertex : [ list of all TreeNodes under root_vertex ] }
426
427
x[1] = (v1, v2, v3) tuple of the three external vertices (also
428
the roots of each tree)
429
EXAMPLES::
430
431
sage: from sage.graphs.schnyder import _triangulate, _normal_label, _realizer, _compute_coordinates
432
sage: g = Graph(graphs.CycleGraph(7))
433
sage: g.is_planar(set_embedding=True)
434
True
435
sage: faces = g.trace_faces(g._embedding)
436
sage: _triangulate(g, g._embedding)
437
[(2, 0), (4, 2), (6, 4), (5, 0), (3, 5), (1, 3), (4, 0), (3, 0)]
438
sage: tn = _normal_label(g, g._embedding, faces[0])
439
sage: r = _realizer(g, tn)
440
sage: _compute_coordinates(g,r)
441
sage: g.get_pos()
442
{0: [5, 1], 1: [0, 5], 2: [1, 0], 3: [1, 4], 4: [2, 1], 5: [2, 3], 6: [3, 2]}
443
"""
444
445
tree_nodes, (v1, v2, v3) = x
446
# find the roots of each tree:
447
t1, t2, t3 = tree_nodes[v1][0], tree_nodes[v2][1], tree_nodes[v3][2]
448
449
# Compute the number of descendants and depth of each node in
450
# each tree.
451
t1.compute_number_of_descendants()
452
t2.compute_number_of_descendants()
453
t3.compute_number_of_descendants()
454
455
t1.compute_depth_of_self_and_children()
456
t2.compute_depth_of_self_and_children()
457
t3.compute_depth_of_self_and_children()
458
459
coordinates = {} # the dict to pass to g.set_pos()
460
461
# Setting coordinates for external vertices
462
coordinates[t1.label] = [g.order() - 2, 1]
463
coordinates[t2.label] = [0, g.order() - 2]
464
coordinates[t3.label] = [1, 0]
465
466
for v in g.vertices():
467
if v not in [t1.label,t2.label,t3.label]:
468
# Computing coordinates for v
469
r = list((0,0,0))
470
471
for i in [0,1,2]:
472
# Computing size of region i:
473
474
# Tracing up tree (i + 1) % 3
475
p = tree_nodes[v][(i + 1) % 3]
476
while p is not None:
477
q = tree_nodes[p.label][i].number_of_descendants
478
# Adding number of descendants from Tree i nodes with
479
# labels on path up tree (i + 1) % 3
480
r[i] += q
481
p = p.parent
482
483
# Tracing up tree (i - 1) % 3
484
p = tree_nodes[v][(i - 1) % 3]
485
while p is not None:
486
q = tree_nodes[p.label][i].number_of_descendants
487
# Adding number of descendants from Tree i nodes with
488
# labels on path up tree (i - 1) % 3
489
r[i] += q
490
p = p.parent
491
492
q = tree_nodes[v][i].number_of_descendants
493
# Subtracting
494
r[i] -= q
495
496
# Subtracting
497
q = tree_nodes[v][(i-1)%3].depth
498
r[i] -= q
499
500
if sum(r) != g.order() - 1:
501
raise Exception("Computing coordinates failed: vertex %s's coordinates sum to %s. Expected %s"%(v,sum(r),g.order()-1))
502
503
coordinates[v] = r[:-1]
504
505
g.set_pos(coordinates) # Setting _pos attribute to store coordinates
506
507
class TreeNode():
508
"""
509
A class to represent each node in the trees used by _realizer() and
510
_compute_coordinates() when finding a planar geometric embedding in
511
the grid.
512
513
Each tree node is doubly linked to its parent and children.
514
515
INPUT:
516
parent -- the parent TreeNode of self
517
children -- a list of TreeNode children of self
518
label -- the associated realizer vertex label
519
520
EXAMPLES::
521
522
sage: from sage.graphs.schnyder import TreeNode
523
sage: tn = TreeNode(label=5)
524
sage: tn2 = TreeNode(label=2,parent=tn)
525
sage: tn3 = TreeNode(label=3)
526
sage: tn.append_child(tn3)
527
sage: tn.compute_number_of_descendants()
528
2
529
sage: tn.number_of_descendants
530
2
531
sage: tn3.number_of_descendants
532
1
533
sage: tn.compute_depth_of_self_and_children()
534
sage: tn3.depth
535
2
536
537
"""
538
def __init__(self, parent = None, children = None, label = None):
539
"""
540
INPUT:
541
parent -- the parent TreeNode of self
542
children -- a list of TreeNode children of self
543
label -- the associated realizer vertex label
544
545
EXAMPLE::
546
547
sage: from sage.graphs.schnyder import TreeNode
548
sage: tn = TreeNode(label=5)
549
sage: tn2 = TreeNode(label=2,parent=tn)
550
sage: tn3 = TreeNode(label=3)
551
sage: tn.append_child(tn3)
552
sage: tn.compute_number_of_descendants()
553
2
554
sage: tn.number_of_descendants
555
2
556
sage: tn3.number_of_descendants
557
1
558
sage: tn.compute_depth_of_self_and_children()
559
sage: tn3.depth
560
2
561
562
"""
563
if children is None:
564
children = []
565
self.parent = parent
566
self.children = children
567
self.label = label
568
self.number_of_descendants = 1
569
570
571
def compute_number_of_descendants(self):
572
"""
573
Computes the number of descendants of self and all descendants.
574
For each TreeNode, sets result as attribute self.number_of_descendants
575
576
EXAMPLES::
577
578
sage: from sage.graphs.schnyder import TreeNode
579
sage: tn = TreeNode(label=5)
580
sage: tn2 = TreeNode(label=2,parent=tn)
581
sage: tn3 = TreeNode(label=3)
582
sage: tn.append_child(tn3)
583
sage: tn.compute_number_of_descendants()
584
2
585
sage: tn.number_of_descendants
586
2
587
sage: tn3.number_of_descendants
588
1
589
sage: tn.compute_depth_of_self_and_children()
590
sage: tn3.depth
591
2
592
593
"""
594
n = 1
595
for child in self.children:
596
n += child.compute_number_of_descendants()
597
self.number_of_descendants = n
598
return n
599
600
def compute_depth_of_self_and_children(self):
601
"""
602
Computes the depth of self and all descendants.
603
For each TreeNode, sets result as attribute self.depth
604
605
EXAMPLES::
606
607
sage: from sage.graphs.schnyder import TreeNode
608
sage: tn = TreeNode(label=5)
609
sage: tn2 = TreeNode(label=2,parent=tn)
610
sage: tn3 = TreeNode(label=3)
611
sage: tn.append_child(tn3)
612
sage: tn.compute_number_of_descendants()
613
2
614
sage: tn.number_of_descendants
615
2
616
sage: tn3.number_of_descendants
617
1
618
sage: tn.compute_depth_of_self_and_children()
619
sage: tn3.depth
620
2
621
622
"""
623
if self.parent is None:
624
self.depth = 1
625
else:
626
self.depth = self.parent.depth + 1
627
for child in self.children:
628
child.compute_depth_of_self_and_children()
629
630
def append_child(self, child):
631
"""
632
Add a child to list of children.
633
634
EXAMPLES::
635
636
sage: from sage.graphs.schnyder import TreeNode
637
sage: tn = TreeNode(label=5)
638
sage: tn2 = TreeNode(label=2,parent=tn)
639
sage: tn3 = TreeNode(label=3)
640
sage: tn.append_child(tn3)
641
sage: tn.compute_number_of_descendants()
642
2
643
sage: tn.number_of_descendants
644
2
645
sage: tn3.number_of_descendants
646
1
647
sage: tn.compute_depth_of_self_and_children()
648
sage: tn3.depth
649
2
650
651
"""
652
if child in self.children:
653
return
654
self.children.append(child)
655
child.parent = self
656
657