Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
""" Schnyder's Algorithm for straight-line planar embeddings
A module for computing the (x,y) coordinates for a straight-line planar embedding of any connected planar graph with at least three vertices. Uses Walter Schnyder's Algorithm.
AUTHORS:
- Jonathan Bober, Emily Kirkman (2008-02-09) -- initial version
REFERENCE:
.. [1] Schnyder, Walter. Embedding Planar Graphs on the Grid. Proc. 1st Annual ACM-SIAM Symposium on Discrete Algorithms, San Francisco (1994), pp. 138-147. """ # **************************************************************************** # Copyright (C) 2008 Jonathan Bober and Emily Kirkman # # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ # ****************************************************************************
""" Helper function to schnyder method for computing coordinates in the plane to plot a planar graph with no edge crossings.
Given a connected graph g with at least 3 vertices and a planar combinatorial embedding comb_emb of g, modify g in place to form a graph whose faces are all triangles, and return the set of newly created edges.
The simple way to triangulate a face is to just pick a vertex and draw an edge from that vertex to every other vertex in the face. Think that this might ultimately result in graphs that don't look very nice when we draw them so we have decided on a different strategy. After handling special cases, we add an edge to connect every third vertex on each face. If this edge is a repeat, we keep the first edge of the face and retry the process at the next edge in the face list. (By removing the special cases we guarantee that this method will work on one of these attempts.)
INPUT:
- g -- the graph to triangulate - ``comb_emb`` -- a planar combinatorial embedding of g
OUTPUT:
A list of edges that are added to the graph (in place)
EXAMPLES::
sage: from sage.graphs.schnyder import _triangulate sage: g = Graph(graphs.CycleGraph(4)) sage: g.is_planar(set_embedding=True) True sage: _triangulate(g, g._embedding) [(2, 0), (1, 3)]
sage: g = graphs.PathGraph(3) sage: g.is_planar(set_embedding=True) True sage: _triangulate(g, g._embedding) [(0, 2)] """ # first make sure that the graph has at least 3 vertices, and that it is connected raise ValueError("A Graph with less than 3 vertices doesn't have any triangulation.") raise NotImplementedError("_triangulate() only knows how to handle connected graphs.")
new_edge = (vertex_list[1], vertex_list[2]) # and connect the other two together. else: new_edge = (vertex_list[0], vertex_list[1])
# At this point we know that the graph is connected, has at least 3 vertices, and # that it is not the graph o--o--o. This is where the real work starts.
# We start by finding all of the faces of this embedding.
# This will be returned at the end.
raise RuntimeError('Triangulate method created face %s with < 3 edges.' % face) new_face = (face[2][1], face[1][0]) else: break
r""" Helper function to schnyder method for computing coordinates in the plane to plot a planar graph with no edge crossings.
Constructs a normal labelling of a triangular graph g, given the planar combinatorial embedding of g and a designated external face. Returns labels dictionary. The normal label is constructed by first contracting the graph down to its external face, then expanding the graph back to the original while simultaneously adding angle labels.
INPUT:
- g -- the graph to find the normal labeling of (g must be triangulated) - ``comb_emb`` -- a planar combinatorial embedding of g - ``external_face`` -- the list of three edges in the external face of g
OUTPUT:
x -- tuple with entries
x[0] = dict of dicts of normal labeling for each vertex of g and each adjacent neighbors u,v (u < v) of vertex:
{ vertex : { (u,v): angel_label } }
x[1] = (v1,v2,v3) tuple of the three vertices of the external face.
EXAMPLES::
sage: from sage.graphs.schnyder import _triangulate, _normal_label, _realizer sage: g = Graph(graphs.CycleGraph(7)) sage: g.is_planar(set_embedding=True) True sage: faces = g.faces(g._embedding) sage: _triangulate(g, g._embedding) [(2, 0), (4, 2), (6, 4), (1, 3), (6, 1), (3, 5), (4, 0), (6, 3)] sage: tn = _normal_label(g, g._embedding, faces[0]) sage: _realizer(g, tn) ({0: [<sage.graphs.schnyder.TreeNode object at ...>]}, (0, 1, 2)) """
external_face[1][0], external_face[2][0]])
# contraction phase:
except Exception: raise RuntimeError('Contractible list is empty but graph still has %d vertices. (Expected 3.)' % g.order())
break # going to contract v v_neighbors - v1_neighbors - Set([v1]))) # adding edge (v1, w)
# expansion phase:
# going to add back vertex v
# we are adding v into the face new_neighbors
(w2, w3): labels[w1].pop((w2, w3)), (w1, w3): labels[w2].pop((w1, w3))}
else: # find a unique element in l else:
else:
# is w[0] a 2 or a 3? else:
# delete all the extra labels
""" Given a triangulated graph g and a normal labeling constructs the realizer and returns a dictionary of three trees determined by the realizer, each spanning all interior vertices and rooted at one of the three external vertices.
A realizer is a directed graph with edge labels that span all interior vertices from each external vertex. It is determined by giving direction to the edges that have the same angle label on both sides at a vertex. (Thus the direction actually points to the parent in the tree.) The edge label is set as whatever the matching angle label is. Then from any interior vertex, following the directed edges by label will give a path to each of the three external vertices.
INPUT:
- g -- the graph to compute the realizer of - x -- tuple with entries
x[0] = dict of dicts representing a normal labeling of g. For each vertex of g and each adjacent neighbors u,v (u < v) of vertex: { vertex : { (u,v): angle_label } }
x[1] = (v1, v2, v3) tuple of the three external vertices (also the roots of each tree)
OUTPUT:
- x -- tuple with entries
x[0] = dict of lists of TreeNodes:
{ root_vertex : [ list of all TreeNodes under root_vertex ] }
x[1] = (v1,v2,v3) tuple of the three external vertices (also the roots of each tree)
EXAMPLES::
sage: from sage.graphs.schnyder import _triangulate, _normal_label, _realizer sage: g = Graph(graphs.CycleGraph(7)) sage: g.is_planar(set_embedding=True) True sage: faces = g.faces(g._embedding) sage: _triangulate(g, g._embedding) [(2, 0), (4, 2), (6, 4), (1, 3), (6, 1), (3, 5), (4, 0), (6, 3)] sage: tn = _normal_label(g, g._embedding, faces[0]) sage: _realizer(g, tn) ({0: [<sage.graphs.schnyder.TreeNode object at ...>]}, (0, 1, 2))
"""
TreeNode(label=v, children=[]), TreeNode(label=v, children=[])]
realizer.show(talk=True, edge_labels=True)
r""" Given a triangulated graph g with a dict of trees given by the realizer and tuple of the external vertices, we compute the coordinates of a planar geometric embedding in the grid.
The coordinates will be set to the ``_pos`` attribute of g.
INPUT:
- g -- the graph to compute the coordinates of - x -- tuple with entries
x[0] = dict of tree nodes for the three trees with each external vertex as root:
{ root_vertex : [ list of all TreeNodes under root_vertex ] }
x[1] = (v1, v2, v3) tuple of the three external vertices (also the roots of each tree)
EXAMPLES::
sage: from sage.graphs.schnyder import _triangulate, _normal_label, _realizer, _compute_coordinates sage: g = Graph(graphs.CycleGraph(7)) sage: g.is_planar(set_embedding=True) True sage: faces = g.faces(g._embedding) sage: _triangulate(g, g._embedding) [(2, 0), (4, 2), (6, 4), (1, 3), (6, 1), (3, 5), (4, 0), (6, 3)] sage: tn = _normal_label(g, g._embedding, faces[0]) sage: r = _realizer(g, tn) sage: _compute_coordinates(g,r) sage: g.get_pos() {0: [5, 1], 1: [0, 5], 2: [1, 0], 3: [1, 3], 4: [2, 1], 5: [2, 2], 6: [3, 2]} """
# find the roots of each tree:
# Compute the number of descendants and depth of each node in # each tree.
# Setting coordinates for external vertices
# Computing coordinates for v
# Computing size of region i:
# Tracing up tree (i + 1) % 3 # Adding number of descendants from Tree i nodes with # labels on path up tree (i + 1) % 3
# Tracing up tree (i - 1) % 3 # Adding number of descendants from Tree i nodes with # labels on path up tree (i - 1) % 3
# Subtracting
# Subtracting
raise RuntimeError("Computing coordinates failed: vertex %s's coordinates sum to %s. Expected %s" % (v, sum(r), g.order() - 1))
""" A class to represent each node in the trees used by ``_realizer`` and ``_compute_coordinates`` when finding a planar geometric embedding in the grid.
Each tree node is doubly linked to its parent and children.
INPUT:
- ``parent`` -- the parent TreeNode of ``self`` - ``children`` -- a list of TreeNode children of ``self`` - ``label`` -- the associated realizer vertex label
EXAMPLES::
sage: from sage.graphs.schnyder import TreeNode sage: tn = TreeNode(label=5) sage: tn2 = TreeNode(label=2,parent=tn) sage: tn3 = TreeNode(label=3) sage: tn.append_child(tn3) sage: tn.compute_number_of_descendants() 2 sage: tn.number_of_descendants 2 sage: tn3.number_of_descendants 1 sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 """ """ INPUT:
- ``parent`` -- the parent TreeNode of ``self`` - ``children`` -- a list of TreeNode children of ``self`` - ``label`` -- the associated realizer vertex label
EXAMPLES::
sage: from sage.graphs.schnyder import TreeNode sage: tn = TreeNode(label=5) sage: tn2 = TreeNode(label=2,parent=tn) sage: tn3 = TreeNode(label=3) sage: tn.append_child(tn3) sage: tn.compute_number_of_descendants() 2 sage: tn.number_of_descendants 2 sage: tn3.number_of_descendants 1 sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 """
""" Computes the number of descendants of self and all descendants.
For each TreeNode, sets result as attribute self.number_of_descendants
EXAMPLES::
sage: from sage.graphs.schnyder import TreeNode sage: tn = TreeNode(label=5) sage: tn2 = TreeNode(label=2,parent=tn) sage: tn3 = TreeNode(label=3) sage: tn.append_child(tn3) sage: tn.compute_number_of_descendants() 2 sage: tn.number_of_descendants 2 sage: tn3.number_of_descendants 1 sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2
"""
""" Computes the depth of self and all descendants.
For each TreeNode, sets result as attribute self.depth
EXAMPLES::
sage: from sage.graphs.schnyder import TreeNode sage: tn = TreeNode(label=5) sage: tn2 = TreeNode(label=2,parent=tn) sage: tn3 = TreeNode(label=3) sage: tn.append_child(tn3) sage: tn.compute_number_of_descendants() 2 sage: tn.number_of_descendants 2 sage: tn3.number_of_descendants 1 sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 """ else:
""" Add a child to list of children.
EXAMPLES::
sage: from sage.graphs.schnyder import TreeNode sage: tn = TreeNode(label=5) sage: tn2 = TreeNode(label=2,parent=tn) sage: tn3 = TreeNode(label=3) sage: tn.append_child(tn3) sage: tn.compute_number_of_descendants() 2 sage: tn.number_of_descendants 2 sage: tn3.number_of_descendants 1 sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 """ return
""" Return the minimal Schnyder wood of a planar rooted triangulation.
INPUT:
- graph -- a planar triangulation, given by a graph with an embedding.
- root_edge -- a pair of vertices (default is from ``'a'`` to ``'b'``) The third boundary vertex is then determined using the orientation and will be labelled ``'c'``.
- minimal -- boolean (default ``True``), whether to return a minimal or a maximal Schnyder wood.
- check -- boolean (default ``True``), whether to check if the input is a planar triangulation
OUTPUT:
a planar graph, with edges oriented and colored. The three outer edges of the initial graph are removed.
The algorithm is taken from [Brehm2000]_ (section 4.2).
EXAMPLES::
sage: from sage.graphs.schnyder import minimal_schnyder_wood sage: g = Graph([(0,'a'),(0,'b'),(0,'c'),('a','b'),('b','c'), ....: ('c','a')], format='list_of_edges') sage: g.set_embedding({'a':['b',0,'c'],'b':['c',0,'a'], ....: 'c':['a',0,'b'],0:['a','b','c']}) sage: newg = minimal_schnyder_wood(g) sage: newg.edges() [(0, 'a', 'green'), (0, 'b', 'blue'), (0, 'c', 'red')] sage: newg.plot(color_by_label={'red':'red','blue':'blue', ....: 'green':'green',None:'black'}) Graphics object consisting of 8 graphics primitives
A larger example::
sage: g = Graph([(0,'a'),(0,2),(0,1),(0,'c'),('a','c'),('a',2), ....: ('a','b'),(1,2),(1,'c'),(2,'b'),(1,'b'),('b','c')], format='list_of_edges') sage: g.set_embedding({'a':['b',2,0,'c'],'b':['c',1,2,'a'], ....: 'c':['a',0,1,'b'],0:['a',2,1,'c'],1:['b','c',0,2],2:['a','b',1,0]}) sage: newg = minimal_schnyder_wood(g) sage: sorted(newg.edges(), key=lambda e:(str(e[0]),str(e[1]))) [(0, 2, 'blue'), (0, 'a', 'green'), (0, 'c', 'red'), (1, 0, 'green'), (1, 'b', 'blue'), (1, 'c', 'red'), (2, 1, 'red'), (2, 'a', 'green'), (2, 'b', 'blue')] sage: newg2 = minimal_schnyder_wood(g, minimal=False) sage: sorted(newg2.edges(), key=lambda e:(str(e[0]),str(e[1]))) [(0, 1, 'blue'), (0, 'a', 'green'), (0, 'c', 'red'), (1, 2, 'green'), (1, 'b', 'blue'), (1, 'c', 'red'), (2, 0, 'red'), (2, 'a', 'green'), (2, 'b', 'blue')]
TESTS::
sage: minimal_schnyder_wood(graphs.RandomTriangulation(5)) Digraph on 5 vertices sage: minimal_schnyder_wood(graphs.CompleteGraph(5)) Traceback (most recent call last): ... ValueError: not a planar graph sage: minimal_schnyder_wood(graphs.WheelGraph(5)) Traceback (most recent call last): ... ValueError: not a triangulation sage: minimal_schnyder_wood(graphs.OctahedralGraph(),root_edge=(0,5)) Traceback (most recent call last): ... ValueError: not a valid root edge
REFERENCES:
.. [Brehm2000] Enno Brehm, *3-Orientations and Schnyder 3-Tree-Decompositions*, 2000 """ else:
# finding the third outer vertex c
# initialisation
for i in graph} u != a and u != b]
# iterated path shortening else: # updating the table of neighbors_in_path # updating removable nodes u != a and u != b]
v in [a, b, c])] for v in graph}
|