It took some time, and first I added new "edge label" feature to JavaScript graph library Postscript output yesterday:
viewtopic.php?p=1997149#p1997149
Then I ported Postscript drawing stuff from ps.js to ps.py, and created new embedding_demo.py not available for JavaScript right now, which I will discuss below.
First after initial posting a word on design decisions for JavaScript and Python undirected_graph libs. It was important to have an edge representation, being able to access both end vertices (with source(G, e) and target(G, e)), and most importantly, to be able to find next cyclic adjacanet edge of edge e for vertex v in constant time. For that I used 2-dimensional array G.V and 3-dimensional array G.E in JavaScript. I just used same square bracket representation in Python, but that are lists in Python. Lets take the print_graph() output for graph "K5-e" shown in previous posting. The complete graph on 5 vertices (that is non-planar), with one edge removed (between vertices 1 and 3, that graph is a planar graph):
Code: Select all
K5-e: 5 vertices, 9 edges
0: (0)1 (1)2 (2)3 (3)4
1: (4)2 (0)0 (5)4
2: (6)4 (7)3 (1)0 (4)1
3: (8)4 (2)0 (7)2
4: (5)1 (3)0 (8)3 (6)2
Adding "print(K.V)" prints the vertex representation 2-dimenssional list:
Code: Select all
[[0, 1, 2, 3], [4, 0, 5], [6, 7, 1, 4], [8, 2, 7], [5, 3, 8, 6]]
Adding "print(K.E)" prints the edge representation 3-dimensional list:
Code: Select all
[[[0, 0], [1, 1]], [[0, 1], [2, 2]], [[0, 2], [3, 1]], [[0, 3], [4, 1]], [[1, 0], [2, 3]], [[1, 2], [4, 0]], [[2, 0], [4, 3]], [[2, 1], [3, 2]], [[3, 0], [4, 2]]]
Looking eg. at edge "8" between vertices "3" and "4", edge 8 appears at 0th and 2nd position in adjacent edge lists G.V[3] and G.V[4].
G.E[8] is [[3, 0], [4, 2]], with 2-lists for vertex "3" (first entry) and "4" (2nd entry). The 2nd entries in the 2-lists are the indices where edge 8 apppears in adjacent edge lists of vertices 3 and 4.
"next_adjacent_edge(G, v, e)" function is essential for planar graph traversing, and the reason I created this library and do not use Python networkx lib.
I get the embeddings into graph form from vertex adjacent vertex list by "from_adjacency_list(L)".
"embedding_demo.py" is described here:
https://github.com/Hermann-SW/planar_gr ... ing_demopy
The adjacency list graph K4 (complete graph on 4 vertices) corresponds to the clockwise order of edges in graph drawing. The edge labels have the face number first, and letters in order of traversal of the face in at the end. Since the number of faces traversed for K4 is 2 + n_vertices(G) - n_edges(G) = 4, the graph is an embedding:

- postscript_traversal_edge_labels.K4.png
- postscript_traversal_edge_labels.K4.png (23.59 KiB) Viewed 2098 times
Now a slightly different edge order corresponding to this drawing for graph K4noemb is no embedding. Reason is that only 2 "faces" get traversed (always using "next_adjacent_edge()", next edge in clockwise order), where 4 faces are needed for a planar embedding:

- postscript_traversal_edge_labels.K4noemb.png
- postscript_traversal_edge_labels.K4noemb.png (24.42 KiB) Viewed 2098 times
All details can be found in embedding_demo.py source:
https://github.com/Hermann-SW/planar_gr ... ng_demo.py
I want to highlight the planar face traversal that computed the edge labels here.
Function "incr()" to increment an array entry by one can be found here:
https://github.com/Hermann-SW/planar_gr ... py#L10-L11
Function "assgn()" to assign an array entry with a value can be found here:
https://github.com/Hermann-SW/planar_gr ... py#L39-L40
Code: Select all
def assgn(G, v, e, arr, x):
arr[e][ind(G, v, e)] = x
These simple helper functions are needed, since assignments are not possible in Python lambda expressions.
This is code for determining the edge labels. eface has two entries for each edge, initialized with -1. Later the displayed label will be stored. Determining which of the two locations to store for edge is is done with "ind(G, v, e)" function, which returns 0 if v is source of e, and 1 otherwise. That way for both directions of traversal of edge e data can be stored. Variables need to be passed by reference in the lambdas, so a 1-element list is used instead of integer variables last_face and cnt. The planar_face_traversal_visitor class is motivated by C++ Boost graph library, and is filled wih dummy functions doing nothing by default.
https://github.com/Hermann-SW/planar_gr ... py#L21-L34
Below code does increment last_face and sets cnt to 0 on entering a new face (begin_face).
And it assigns new edge label to eface[e]... and increments cnt for every edge visited on face traversal (next_vertex_edge).
Finally call "planar_face_traversal(G, pftv)" does the job:
Code: Select all
eface = filled_array(n_edges(G), 2, -1)
last_face = [-1]
cnt = [-1]
pftv = planar_face_traversal_visitor()
pftv.begin_face = lambda: (incr(last_face), aset(cnt,0))
pftv.next_vertex_edge = lambda v, e: (assgn(G, v, e, eface,
str(last_face[0])+"_"+chr(97+cnt[0])),
incr(cnt))
planar_face_traversal(G, pftv)
And this simple loop does add the labels to the edges (on correct side, with correct angle):
https://github.com/Hermann-SW/planar_gr ... py#L53-L64
First source and target of edge e get computed.
Then middle point cx/cy of drawn edge between v and w.
atan2() function determines the angle needed for drawing label parallel to edge e, on correct side.
Then both edge labels are printed in Postscript with the determined angle deg and incremented by 180° for the other side.
This is all done in helper function "def draw_edge_label(G, e, coords)", which is called in forall_edges() loop at bottom:
Code: Select all
def draw_edge_label(G, e, coords):
v = source(G, e)
w = target(G, e)
cx = (ps.scrx(coords[0][v]) + ps.scrx(coords[0][w])) / 2
cy = (ps.scry(coords[1][v]) + ps.scry(coords[1][w])) / 2
deg = math.atan2(coords[1][v] - coords[1][w], coords[0][w] - coords[0][v]) * 180 / math.pi
print("12 " + str(deg) + " (" + eface[e][0] + ") " +
str(cx) + " " + str(cy) + " txtdistdeg")
print("12 " + str(180+deg) + " (" + eface[e][1] + ") " +
str(cx) + " " + str(cy) + " txtdistdeg")
forall_edges(G, lambda e: draw_edge_label(G, e, coords))
I will bring Python port to parity with JavaScript version of lib soon, and then extend both with new features.
I selected MIT license for planar_graph_playground repo so that you can maximally make use of this lib.
What definitely will come is computing of planar embedding for maximal planar graphs, which I created a technical report on back in 1993 as researcher at computer science department of University Bonn/Germany. That report has an algorithm for creating random maximal planar graphs as well, which will definitely be imported here as well:
"A simple linear time algorithm for embedding maximal planar graphs"
https://stamm-wilbrandt.de/en/TR/IAI-TR-93-10.ps.pdf
P.S:
I forgot to mention that I am done with pylint work:
https://github.com/Hermann-SW/planar_gr ... on/rpylint
I had to ignore warnings of passing more than 5 arguments to a function, 8 args are needed for one function in order to avoid use of global variables in it:
https://github.com/Hermann-SW/planar_gr ... /ps.py#L20
rpylint creates temporary file "/tmp/rpylint", which can show you which line the linter or assert statements when executed with "./rpy" are meant.
Down to a single warning (of those not silenced in rpylint) by having to use global variable "lookup" for implementing "from_adjacency_list()":
https://github.com/Hermann-SW/planar_gr ... y#L97-L112
Code: Select all
pi@pi400-64:~/planar_graph_playground/python $ ./rpylint embedding_demo.py
************* Module rpylint
/tmp/rpylint:102:4: W0603: Using the global statement (global-statement)
------------------------------------------------------------------
Your code has been rated at 9.97/10 (previous run: 9.97/10, +0.00)
pi@pi400-64:~/planar_graph_playground/python $