def comps_to_nx_planar_embedding(components): """Set of connected planar graphs (possibly derived) to nx.PlanarEmbedding.""" res = nx.PlanarEmbedding() for g in components: g = g.underive_all() g = g.to_planar_embedding(relabel=False) res = nx.PlanarEmbedding(nx.compose(res, g)) # Relabel once all the components are in the same graph. # relabel_networkx(res) return res
def test_missing_edge_orientation(self): with pytest.raises(nx.NetworkXException): embedding = nx.PlanarEmbedding() embedding.add_edge(1, 2) embedding.add_edge(2, 1) # Invalid structure because the orientation of the edge was not set embedding.check_structure()
def test_invalid_edge_orientation(self): with pytest.raises(nx.NetworkXException): embedding = nx.PlanarEmbedding() embedding.add_half_edge_first(1, 2) embedding.add_half_edge_first(2, 1) embedding.add_edge(1, 3) embedding.check_structure()
def test_not_fulfilling_euler_formula(self): embedding = nx.PlanarEmbedding() for i in range(5): for j in range(5): if i != j: embedding.add_half_edge_first(i, j) embedding.check_structure()
def test_not_fulfilling_euler_formula(self): with pytest.raises(nx.NetworkXException): embedding = nx.PlanarEmbedding() for i in range(5): for j in range(5): if i != j: embedding.add_half_edge_first(i, j) embedding.check_structure()
def test_invalid_half_edge(): with pytest.raises(nx.NetworkXException): embedding_data = { 1: [2, 3, 4], 2: [1, 3, 4], 3: [1, 2, 4], 4: [1, 2, 3] } embedding = nx.PlanarEmbedding() embedding.set_data(embedding_data) nx.combinatorial_embedding_to_pos(embedding)
def check_embedding_data(embedding_data): """Checks that the planar embedding of the input is correct""" embedding = nx.PlanarEmbedding() embedding.set_data(embedding_data) pos_fully = nx.combinatorial_embedding_to_pos(embedding, False) msg = "Planar drawing does not conform to the embedding (fully " "triangulation)" assert planar_drawing_conforms_to_embedding(embedding, pos_fully), msg check_edge_intersections(embedding, pos_fully) pos_internally = nx.combinatorial_embedding_to_pos(embedding, True) msg = "Planar drawing does not conform to the embedding (internal " "triangulation)" assert planar_drawing_conforms_to_embedding(embedding, pos_internally), msg check_edge_intersections(embedding, pos_internally)
def get_dual(primal): """ Returns the dual of a planar embedding. Parameters ---------- primal: nx.PlanarEmbedding Returns ------- primal_faces: dict A dict whose values are lists representing facial walks of the primal and whose keys are its dual nodes. dual: nx.PlanarEmbedding The dual of the primal. dual_faces: dict A dict whose values are lists representing facial walks of the dual and whose keys are nodes of the primal. """ if not isinstance(primal, nx.PlanarEmbedding): print("'primal' must be a planar embedding.") # Constracting the dual embedding of 'primal'. primal_faces = get_faces(primal) dual = nx.PlanarEmbedding() for tail, primal_face in primal_faces.items(): # Define a clockwize rotation around tail in dual nodes. pre_head = None for i in range(len(primal_face)): u = primal_face[i] v = primal_face[(i + 1) % len(primal_face)] head = primal.edges[v, u]['dual_node'] dual.add_half_edge_cw(tail, head, pre_head) pre_head = head primal.edges[v, u]['right_half_edge'] = (tail, head) dual.edges[tail, head]['dual_node'] = u dual.edges[tail, head]['right_half_edge'] = (u, v) # A face of 'dual' can be derived from # clockwize rotation around a node of 'primal'. dual.check_structure() dual_faces = {} for u in primal.nodes: dual_face = [] for v in primal.neighbors_cw_order(u): dual_face.append(primal.edges[u, v]['right_half_edge'][0]) dual_faces[u] = dual_face return primal_faces, dual, dual_faces
def convert_pos_to_embedding(G, pos): """ Only straight line in G. """ emd = nx.PlanarEmbedding() for node in G: neigh_pos = { neigh: (pos[neigh][0]-pos[node][0], pos[neigh][1]-pos[node][1]) for neigh in G[node] } neighbors_sorted = sorted(G.adj[node], key=lambda v: m.atan2(neigh_pos[v][1], neigh_pos[v][0])) # counter clockwise last = None for neigh in neighbors_sorted: emd.add_half_edge_ccw(node, neigh, last) last = neigh emd.check_structure() return emd
def main(): # Cycle graph _, embedding = nx.check_planarity(nx.cycle_graph(20)) embedding_fully, _ = nx.triangulate_embedding(embedding, True) diff_fully = nx.Graph(list(embedding_fully.edges - embedding.edges)) embedding_internal, _ = nx.triangulate_embedding(embedding, False) diff_internal = nx.Graph(list(embedding_internal.edges - embedding.edges)) pos = nx.combinatorial_embedding_to_pos(embedding_fully) nx.draw(diff_fully, pos, alpha=0.5, width=1, style="dotted", node_size=30) nx.draw(embedding, pos, width=2 , node_size=30) plt.savefig("drawing_cycle_fully_triangulated.png", format="PNG") plt.show() pos = nx.combinatorial_embedding_to_pos(embedding_internal) nx.draw(diff_internal, pos, alpha=0.5, width=1, style="dotted", node_size=30) nx.draw(embedding, pos, width=2, node_size=30) plt.savefig("drawing_cycle_internally_triangulated.png", format="PNG") plt.show() # Other graph G = nx.Graph([(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (1, 4), (4, 3)]) is_planar, embedding = nx.check_planarity(G) print(is_planar) embedding_fully, _ = nx.triangulate_embedding(embedding, True) diff_fully = nx.Graph(list(embedding_fully.edges - embedding.edges)) embedding_internal, _ = nx.triangulate_embedding(embedding, False) diff_internal = nx.Graph(list(embedding_internal.edges - embedding.edges)) pos = nx.combinatorial_embedding_to_pos(embedding_fully) nx.draw(diff_fully, pos, alpha=0.5, width=1, style="dotted", node_size=30) nx.draw(embedding, pos, width=2, node_size=30) plt.savefig("drawing_other_fully_triangulated.png", format="PNG") plt.show() pos = nx.combinatorial_embedding_to_pos(embedding_internal) nx.draw(diff_internal, pos, alpha=0.5, width=1, style="dotted", node_size=30) nx.draw(embedding, pos, width=2, node_size=30) plt.savefig("drawing_other_internally_triangulated.png", format="PNG") plt.show() embedding_data = {0: [36, 42], 1: [], 2: [23, 16], 3: [19], 4: [23, 17], 5: [45, 18], 6: [42, 29, 40], 7: [48, 26, 32], 8: [15, 44, 23], 9: [11, 27], 10: [39, 11, 32, 47, 26, 15], 11: [10, 9], 12: [41, 34, 35], 13: [48], 14: [28, 45], 15: [34, 8, 10], 16: [2, 39, 21], 17: [4], 18: [5], 19: [22, 3], 20: [], 21: [16, 49], 22: [26, 47, 19], 23: [8, 2, 4], 24: [46], 25: [], 26: [7, 34, 10, 22, 38], 27: [9, 48], 28: [36, 41, 14], 29: [6], 30: [48], 31: [], 32: [7, 10, 46], 33: [48], 34: [12, 15, 26], 35: [12, 41], 36: [0, 28, 43], 37: [47], 38: [26], 39: [16, 10], 40: [6], 41: [28, 12, 35], 42: [44, 0, 6], 43: [36], 44: [8, 42], 45: [14, 5], 46: [32, 24], 47: [22, 10, 37], 48: [27, 7, 13, 30, 33], 49: [21]} embedding = nx.PlanarEmbedding() embedding.set_data(embedding_data) pos = nx.combinatorial_embedding_to_pos(embedding, fully_triangulate=False) nx.draw(embedding, pos, node_size=30) plt.savefig("drawing_large_graph_internally_triangulated.png", format="PNG") plt.show()
def get_planar_embedding(self): """only straight line in G.""" emd = nx.PlanarEmbedding() for node in self.g: neigh_pos = { neigh: (self.pos[neigh][0] - self.pos[node][0], self.pos[neigh][1] - self.pos[node][1]) for neigh in self.g[node] } neighes_sorted = sorted( self.g.adj[node], key=lambda v: math.atan2(neigh_pos[v][1], neigh_pos[v][0] )) # counter clockwise last = None for neigh in neighes_sorted: emd.add_half_edge_ccw(node, neigh, last) last = neigh emd.check_structure() return emd
def get_radial(primal): """ Returns the dual and the radial of a planar embedding. The radial graph of a planar embedding G is also known as the vertex-face map whose vertices are the vertices of G together with the faces of G, and whose edges correspond to the vertex-face incidence in G. Parameters ---------- primal: nx.PlanarEmbedding Returns ------- dual: nx.PlanarEmbedding The dual of the primal. radial: nx.PlanarEmbedding The radial of the primal. """ primal_faces, dual, dual_faces = get_dual(primal) radial = nx.PlanarEmbedding() for p in primal.nodes: p_name = 'p' + str(p) pre_d_name = None for d in dual_faces[p]: d_name = 'd' + str(d) radial.add_half_edge_cw(p_name, d_name, pre_d_name) pre_d_name = d_name radial.nodes[p_name]['primal'] = True radial.nodes[p_name]['original'] = p for d in dual.nodes: d_name = 'd' + str(d) pre_p_name = None for p in primal_faces[d]: p_name = 'p' + str(p) radial.add_half_edge_cw(d_name, p_name, pre_p_name) pre_p_name = p_name radial.nodes[d_name]['primal'] = False radial.nodes[d_name]['original'] = d radial.check_structure() return dual, radial
def to_planar_embedding(self, relabel=True): """Converts to nx.PlanarEmbedding. Returns ------- PlanarEmbedding """ nodes = self.half_edge.node_dict() embedding = nx.PlanarEmbedding() # Loop over all nodes in the graph (node_nr). for node in nodes: embedding.add_node(node) # Loop over all half-edges incident the the current node, in ccw order around the node. reference_neighbour = None for he in nodes[node]: if he.opposite is None: continue embedding.add_half_edge_ccw(node, he.opposite.node_nr, reference_neighbour) reference_neighbour = he.opposite.node_nr if relabel: relabel_networkx(embedding) return embedding
def test_unsuccessful_face_traversal(self): with pytest.raises(nx.NetworkXException): embedding = nx.PlanarEmbedding() embedding.add_edge(1, 2, ccw=2, cw=3) embedding.add_edge(2, 1, ccw=1, cw=3) embedding.traverse_face(1, 2)
def test_successful_face_traversal(self): embedding = nx.PlanarEmbedding() embedding.add_half_edge_first(1, 2) embedding.add_half_edge_first(2, 1) face = embedding.traverse_face(1, 2) assert face == [1, 2]
def test_connect_components(self): embedding = nx.PlanarEmbedding() embedding.connect_components(1, 2)
def test_missing_reference(self): with pytest.raises(nx.NetworkXException): embedding = nx.PlanarEmbedding() embedding.add_half_edge_cw(1, 2, 3)
def test_missing_half_edge(self): with pytest.raises(nx.NetworkXException): embedding = nx.PlanarEmbedding() embedding.add_half_edge_first(1, 2) # Invalid structure because other half edge is missing embedding.check_structure()
def triangulate_embedding(embedding, fully_triangulate=True): """Triangulates the embedding. Traverses faces of the embedding and adds edges to a copy of the embedding to triangulate it. The method also ensures that the resulting graph is 2-connected by adding edges if the same vertex is contained twice on a path around a face. Parameters ---------- embedding : nx.PlanarEmbedding The input graph must contain at least 3 nodes. fully_triangulate : bool If set to False the face with the most nodes is chooses as outer face. This outer face does not get triangulated. Returns ------- (embedding, outer_face) : (nx.PlanarEmbedding, list) tuple The element `embedding` is a new embedding containing all edges from the input embedding and the additional edges to triangulate the graph. The element `outer_face` is a list of nodes that lie on the outer face. If the graph is fully triangulated these are three arbitrary connected nodes. """ if len(embedding.nodes) <= 1: return embedding, list(embedding.nodes) embedding = nx.PlanarEmbedding(embedding) # Get a list with a node for each connected component component_nodes = [next(iter(x)) for x in nx.connected_components(embedding)] # 1. Make graph a single component (add edge between components) for i in range(len(component_nodes) - 1): v1 = component_nodes[i] v2 = component_nodes[i + 1] embedding.connect_components(v1, v2) # 2. Calculate faces, ensure 2-connectedness and determine outer face outer_face = [] # A face with the most number of nodes face_list = [] edges_visited = set() # Used to keep track of already visited faces for v in embedding.nodes(): for w in embedding.neighbors_cw_order(v): new_face = make_bi_connected(embedding, v, w, edges_visited) if new_face: # Found a new face face_list.append(new_face) if len(new_face) > len(outer_face): # The face is a candidate to be the outer face outer_face = new_face # 3. Triangulate (internal) faces for face in face_list: if face is not outer_face or fully_triangulate: # Triangulate this face triangulate_face(embedding, face[0], face[1]) if fully_triangulate: v1 = outer_face[0] v2 = outer_face[1] v3 = embedding[v2][v1]["ccw"] outer_face = [v1, v2, v3] return embedding, outer_face
def test_smoke_planar_layout_embedding_input(self): embedding = nx.PlanarEmbedding() embedding.set_data({0: [1, 2], 1: [0, 2], 2: [0, 1]}) nx.planar_layout(embedding)
def test_missing_edge_orientation(self): embedding = nx.PlanarEmbedding() embedding.add_edge(1, 2) embedding.add_edge(2, 1) # Invalid structure because the orientation of the edge was not set embedding.check_structure()
def test_invalid_half_edge(): embedding_data = {1: [2, 3, 4], 2: [1, 3, 4], 3: [1, 2, 4], 4: [1, 2, 3]} embedding = nx.PlanarEmbedding() embedding.set_data(embedding_data) nx.combinatorial_embedding_to_pos(embedding)
def get_star_embedding(n): embedding = nx.PlanarEmbedding() for i in range(1, n): embedding.add_half_edge_first(0, i) embedding.add_half_edge_first(i, 0) return embedding
def test_missing_half_edge(self): embedding = nx.PlanarEmbedding() embedding.add_half_edge_first(1, 2) # Invalid structure because other half edge is missing embedding.check_structure()
def test_invalid_edge_orientation(self): embedding = nx.PlanarEmbedding() embedding.add_half_edge_first(1, 2) embedding.add_half_edge_first(2, 1) embedding.add_edge(1, 3) embedding.check_structure()
def test_triangulate_embedding2(): embedding = nx.PlanarEmbedding() embedding.connect_components(1, 2) expected_embedding = {1: [2], 2: [1]} check_triangulation(embedding, expected_embedding)
def test_missing_reference(self): embedding = nx.PlanarEmbedding() embedding.add_half_edge_cw(1, 2, 3)
def test_triangulate_embedding1(): embedding = nx.PlanarEmbedding() embedding.add_node(1) expected_embedding = {1: []} check_triangulation(embedding, expected_embedding)
def test_unsuccessful_face_traversal(self): embedding = nx.PlanarEmbedding() embedding.add_edge(1, 2, ccw=2, cw=3) embedding.add_edge(2, 1, ccw=1, cw=3) embedding.traverse_face(1, 2)