diff env/lib/python3.9/site-packages/networkx/algorithms/planar_drawing.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/env/lib/python3.9/site-packages/networkx/algorithms/planar_drawing.py	Mon Mar 22 18:12:50 2021 +0000
@@ -0,0 +1,464 @@
+import networkx as nx
+from collections import defaultdict
+
+
+__all__ = ["combinatorial_embedding_to_pos"]
+
+
+def combinatorial_embedding_to_pos(embedding, fully_triangulate=False):
+    """Assigns every node a (x, y) position based on the given embedding
+
+    The algorithm iteratively inserts nodes of the input graph in a certain
+    order and rearranges previously inserted nodes so that the planar drawing
+    stays valid. This is done efficiently by only maintaining relative
+    positions during the node placements and calculating the absolute positions
+    at the end. For more information see [1]_.
+
+    Parameters
+    ----------
+    embedding : nx.PlanarEmbedding
+        This defines the order of the edges
+
+    fully_triangulate : bool
+        If set to True the algorithm adds edges to a copy of the input
+        embedding and makes it chordal.
+
+    Returns
+    -------
+    pos : dict
+        Maps each node to a tuple that defines the (x, y) position
+
+    References
+    ----------
+    .. [1] M. Chrobak and T.H. Payne:
+        A Linear-time Algorithm for Drawing a Planar Graph on a Grid 1989
+        http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.51.6677
+
+    """
+    if len(embedding.nodes()) < 4:
+        # Position the node in any triangle
+        default_positions = [(0, 0), (2, 0), (1, 1)]
+        pos = {}
+        for i, v in enumerate(embedding.nodes()):
+            pos[v] = default_positions[i]
+        return pos
+
+    embedding, outer_face = triangulate_embedding(embedding, fully_triangulate)
+
+    # The following dicts map a node to another node
+    # If a node is not in the key set it means that the node is not yet in G_k
+    # If a node maps to None then the corresponding subtree does not exist
+    left_t_child = {}
+    right_t_child = {}
+
+    # The following dicts map a node to an integer
+    delta_x = {}
+    y_coordinate = {}
+
+    node_list = get_canonical_ordering(embedding, outer_face)
+
+    # 1. Phase: Compute relative positions
+
+    # Initialization
+    v1, v2, v3 = node_list[0][0], node_list[1][0], node_list[2][0]
+
+    delta_x[v1] = 0
+    y_coordinate[v1] = 0
+    right_t_child[v1] = v3
+    left_t_child[v1] = None
+
+    delta_x[v2] = 1
+    y_coordinate[v2] = 0
+    right_t_child[v2] = None
+    left_t_child[v2] = None
+
+    delta_x[v3] = 1
+    y_coordinate[v3] = 1
+    right_t_child[v3] = v2
+    left_t_child[v3] = None
+
+    for k in range(3, len(node_list)):
+        vk, contour_neighbors = node_list[k]
+        wp = contour_neighbors[0]
+        wp1 = contour_neighbors[1]
+        wq = contour_neighbors[-1]
+        wq1 = contour_neighbors[-2]
+        adds_mult_tri = len(contour_neighbors) > 2
+
+        # Stretch gaps:
+        delta_x[wp1] += 1
+        delta_x[wq] += 1
+
+        delta_x_wp_wq = sum(delta_x[x] for x in contour_neighbors[1:])
+
+        # Adjust offsets
+        delta_x[vk] = (-y_coordinate[wp] + delta_x_wp_wq + y_coordinate[wq]) // 2
+        y_coordinate[vk] = (y_coordinate[wp] + delta_x_wp_wq + y_coordinate[wq]) // 2
+        delta_x[wq] = delta_x_wp_wq - delta_x[vk]
+        if adds_mult_tri:
+            delta_x[wp1] -= delta_x[vk]
+
+        # Install v_k:
+        right_t_child[wp] = vk
+        right_t_child[vk] = wq
+        if adds_mult_tri:
+            left_t_child[vk] = wp1
+            right_t_child[wq1] = None
+        else:
+            left_t_child[vk] = None
+
+    # 2. Phase: Set absolute positions
+    pos = dict()
+    pos[v1] = (0, y_coordinate[v1])
+    remaining_nodes = [v1]
+    while remaining_nodes:
+        parent_node = remaining_nodes.pop()
+
+        # Calculate position for left child
+        set_position(
+            parent_node, left_t_child, remaining_nodes, delta_x, y_coordinate, pos
+        )
+        # Calculate position for right child
+        set_position(
+            parent_node, right_t_child, remaining_nodes, delta_x, y_coordinate, pos
+        )
+    return pos
+
+
+def set_position(parent, tree, remaining_nodes, delta_x, y_coordinate, pos):
+    """Helper method to calculate the absolute position of nodes."""
+    child = tree[parent]
+    parent_node_x = pos[parent][0]
+    if child is not None:
+        # Calculate pos of child
+        child_x = parent_node_x + delta_x[child]
+        pos[child] = (child_x, y_coordinate[child])
+        # Remember to calculate pos of its children
+        remaining_nodes.append(child)
+
+
+def get_canonical_ordering(embedding, outer_face):
+    """Returns a canonical ordering of the nodes
+
+    The canonical ordering of nodes (v1, ..., vn) must fulfill the following
+    conditions:
+    (See Lemma 1 in [2]_)
+
+    - For the subgraph G_k of the input graph induced by v1, ..., vk it holds:
+        - 2-connected
+        - internally triangulated
+        - the edge (v1, v2) is part of the outer face
+    - For a node v(k+1) the following holds:
+        - The node v(k+1) is part of the outer face of G_k
+        - It has at least two neighbors in G_k
+        - All neighbors of v(k+1) in G_k lie consecutively on the outer face of
+          G_k (excluding the edge (v1, v2)).
+
+    The algorithm used here starts with G_n (containing all nodes). It first
+    selects the nodes v1 and v2. And then tries to find the order of the other
+    nodes by checking which node can be removed in order to fulfill the
+    conditions mentioned above. This is done by calculating the number of
+    chords of nodes on the outer face. For more information see [1]_.
+
+    Parameters
+    ----------
+    embedding : nx.PlanarEmbedding
+        The embedding must be triangulated
+    outer_face : list
+        The nodes on the outer face of the graph
+
+    Returns
+    -------
+    ordering : list
+        A list of tuples `(vk, wp_wq)`. Here `vk` is the node at this position
+        in the canonical ordering. The element `wp_wq` is a list of nodes that
+        make up the outer face of G_k.
+
+    References
+    ----------
+    .. [1] Steven Chaplick.
+        Canonical Orders of Planar Graphs and (some of) Their Applications 2015
+        https://wuecampus2.uni-wuerzburg.de/moodle/pluginfile.php/545727/mod_resource/content/0/vg-ss15-vl03-canonical-orders-druckversion.pdf
+    .. [2] M. Chrobak and T.H. Payne:
+        A Linear-time Algorithm for Drawing a Planar Graph on a Grid 1989
+        http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.51.6677
+
+    """
+    v1 = outer_face[0]
+    v2 = outer_face[1]
+    chords = defaultdict(int)  # Maps nodes to the number of their chords
+    marked_nodes = set()
+    ready_to_pick = set(outer_face)
+
+    # Initialize outer_face_ccw_nbr (do not include v1 -> v2)
+    outer_face_ccw_nbr = {}
+    prev_nbr = v2
+    for idx in range(2, len(outer_face)):
+        outer_face_ccw_nbr[prev_nbr] = outer_face[idx]
+        prev_nbr = outer_face[idx]
+    outer_face_ccw_nbr[prev_nbr] = v1
+
+    # Initialize outer_face_cw_nbr (do not include v2 -> v1)
+    outer_face_cw_nbr = {}
+    prev_nbr = v1
+    for idx in range(len(outer_face) - 1, 0, -1):
+        outer_face_cw_nbr[prev_nbr] = outer_face[idx]
+        prev_nbr = outer_face[idx]
+
+    def is_outer_face_nbr(x, y):
+        if x not in outer_face_ccw_nbr:
+            return outer_face_cw_nbr[x] == y
+        if x not in outer_face_cw_nbr:
+            return outer_face_ccw_nbr[x] == y
+        return outer_face_ccw_nbr[x] == y or outer_face_cw_nbr[x] == y
+
+    def is_on_outer_face(x):
+        return x not in marked_nodes and (x in outer_face_ccw_nbr.keys() or x == v1)
+
+    # Initialize number of chords
+    for v in outer_face:
+        for nbr in embedding.neighbors_cw_order(v):
+            if is_on_outer_face(nbr) and not is_outer_face_nbr(v, nbr):
+                chords[v] += 1
+                ready_to_pick.discard(v)
+
+    # Initialize canonical_ordering
+    canonical_ordering = [None] * len(embedding.nodes())  # type: list
+    canonical_ordering[0] = (v1, [])
+    canonical_ordering[1] = (v2, [])
+    ready_to_pick.discard(v1)
+    ready_to_pick.discard(v2)
+
+    for k in range(len(embedding.nodes()) - 1, 1, -1):
+        # 1. Pick v from ready_to_pick
+        v = ready_to_pick.pop()
+        marked_nodes.add(v)
+
+        # v has exactly two neighbors on the outer face (wp and wq)
+        wp = None
+        wq = None
+        # Iterate over neighbors of v to find wp and wq
+        nbr_iterator = iter(embedding.neighbors_cw_order(v))
+        while True:
+            nbr = next(nbr_iterator)
+            if nbr in marked_nodes:
+                # Only consider nodes that are not yet removed
+                continue
+            if is_on_outer_face(nbr):
+                # nbr is either wp or wq
+                if nbr == v1:
+                    wp = v1
+                elif nbr == v2:
+                    wq = v2
+                else:
+                    if outer_face_cw_nbr[nbr] == v:
+                        # nbr is wp
+                        wp = nbr
+                    else:
+                        # nbr is wq
+                        wq = nbr
+            if wp is not None and wq is not None:
+                # We don't need to iterate any further
+                break
+
+        # Obtain new nodes on outer face (neighbors of v from wp to wq)
+        wp_wq = [wp]
+        nbr = wp
+        while nbr != wq:
+            # Get next next neighbor (clockwise on the outer face)
+            next_nbr = embedding[v][nbr]["ccw"]
+            wp_wq.append(next_nbr)
+            # Update outer face
+            outer_face_cw_nbr[nbr] = next_nbr
+            outer_face_ccw_nbr[next_nbr] = nbr
+            # Move to next neighbor of v
+            nbr = next_nbr
+
+        if len(wp_wq) == 2:
+            # There was a chord between wp and wq, decrease number of chords
+            chords[wp] -= 1
+            if chords[wp] == 0:
+                ready_to_pick.add(wp)
+            chords[wq] -= 1
+            if chords[wq] == 0:
+                ready_to_pick.add(wq)
+        else:
+            # Update all chords involving w_(p+1) to w_(q-1)
+            new_face_nodes = set(wp_wq[1:-1])
+            for w in new_face_nodes:
+                # If we do not find a chord for w later we can pick it next
+                ready_to_pick.add(w)
+                for nbr in embedding.neighbors_cw_order(w):
+                    if is_on_outer_face(nbr) and not is_outer_face_nbr(w, nbr):
+                        # There is a chord involving w
+                        chords[w] += 1
+                        ready_to_pick.discard(w)
+                        if nbr not in new_face_nodes:
+                            # Also increase chord for the neighbor
+                            # We only iterator over new_face_nodes
+                            chords[nbr] += 1
+                            ready_to_pick.discard(nbr)
+        # Set the canonical ordering node and the list of contour neighbors
+        canonical_ordering[k] = (v, wp_wq)
+
+    return canonical_ordering
+
+
+def triangulate_face(embedding, v1, v2):
+    """Triangulates the face given by half edge (v, w)
+
+    Parameters
+    ----------
+    embedding : nx.PlanarEmbedding
+    v1 : node
+        The half-edge (v1, v2) belongs to the face that gets triangulated
+    v2 : node
+    """
+    _, v3 = embedding.next_face_half_edge(v1, v2)
+    _, v4 = embedding.next_face_half_edge(v2, v3)
+    if v1 == v2 or v1 == v3:
+        # The component has less than 3 nodes
+        return
+    while v1 != v4:
+        # Add edge if not already present on other side
+        if embedding.has_edge(v1, v3):
+            # Cannot triangulate at this position
+            v1, v2, v3 = v2, v3, v4
+        else:
+            # Add edge for triangulation
+            embedding.add_half_edge_cw(v1, v3, v2)
+            embedding.add_half_edge_ccw(v3, v1, v2)
+            v1, v2, v3 = v1, v3, v4
+        # Get next node
+        _, v4 = embedding.next_face_half_edge(v2, v3)
+
+
+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 make_bi_connected(embedding, starting_node, outgoing_node, edges_counted):
+    """Triangulate a face and make it 2-connected
+
+    This method also adds all edges on the face to `edges_counted`.
+
+    Parameters
+    ----------
+    embedding: nx.PlanarEmbedding
+        The embedding that defines the faces
+    starting_node : node
+        A node on the face
+    outgoing_node : node
+        A node such that the half edge (starting_node, outgoing_node) belongs
+        to the face
+    edges_counted: set
+        Set of all half-edges that belong to a face that have been visited
+
+    Returns
+    -------
+    face_nodes: list
+        A list of all nodes at the border of this face
+    """
+
+    # Check if the face has already been calculated
+    if (starting_node, outgoing_node) in edges_counted:
+        # This face was already counted
+        return []
+    edges_counted.add((starting_node, outgoing_node))
+
+    # Add all edges to edges_counted which have this face to their left
+    v1 = starting_node
+    v2 = outgoing_node
+    face_list = [starting_node]  # List of nodes around the face
+    face_set = set(face_list)  # Set for faster queries
+    _, v3 = embedding.next_face_half_edge(v1, v2)
+
+    # Move the nodes v1, v2, v3 around the face:
+    while v2 != starting_node or v3 != outgoing_node:
+        if v1 == v2:
+            raise nx.NetworkXException("Invalid half-edge")
+        # cycle is not completed yet
+        if v2 in face_set:
+            # v2 encountered twice: Add edge to ensure 2-connectedness
+            embedding.add_half_edge_cw(v1, v3, v2)
+            embedding.add_half_edge_ccw(v3, v1, v2)
+            edges_counted.add((v2, v3))
+            edges_counted.add((v3, v1))
+            v2 = v1
+        else:
+            face_set.add(v2)
+            face_list.append(v2)
+
+        # set next edge
+        v1 = v2
+        v2, v3 = embedding.next_face_half_edge(v2, v3)
+
+        # remember that this edge has been counted
+        edges_counted.add((v1, v2))
+
+    return face_list