예제 #1
0
 def order_to_stitch(self, parents):
     return csgraph.depth_first_order(
         csgraph.reconstruct_path(self.edge_matrix, parents,
                                  directed=False),
         self.center,
         return_predecessors=False,
     )[::-1]
예제 #2
0
    def _compute_period(self):
        """
        Set ``self._period`` and ``self._cyclic_components_proj``.

        Use the algorithm described in:
        J. P. Jarvis and D. R. Shier,
        "Graph-Theoretic Analysis of Finite Markov Chains," 1996.

        """
        # Degenerate graph with a single node (which is strongly connected)
        # csgraph.reconstruct_path would raise an exception
        # github.com/scipy/scipy/issues/4018
        if self.n == 1:
            if self.csgraph[0, 0] == 0:  # No edge: "trivial graph"
                self._period = 1  # Any universally accepted definition?
                self._cyclic_components_proj = np.zeros(self.n, dtype=int)
                return None
            else:  # Self loop
                self._period = 1
                self._cyclic_components_proj = np.zeros(self.n, dtype=int)
                return None

        if not self.is_strongly_connected:
            raise NotImplementedError(
                'Not defined for a non strongly-connected digraph'
            )

        if np.any(self.csgraph.diagonal() > 0):
            self._period = 1
            self._cyclic_components_proj = np.zeros(self.n, dtype=int)
            return None

        # Construct a breadth-first search tree rooted at 0
        node_order, predecessors = \
            csgraph.breadth_first_order(self.csgraph, i_start=0)
        bfs_tree_csr = \
            csgraph.reconstruct_path(self.csgraph, predecessors)

        # Edges not belonging to tree_csr
        non_bfs_tree_csr = self.csgraph - bfs_tree_csr
        non_bfs_tree_csr.eliminate_zeros()

        # Distance to 0
        level = np.zeros(self.n, dtype=int)
        for i in range(1, self.n):
            level[node_order[i]] = level[predecessors[node_order[i]]] + 1

        # Determine the period
        d = 0
        for node_from, node_to in _csr_matrix_indices(non_bfs_tree_csr):
            value = level[node_from] - level[node_to] + 1
            d = gcd(d, value)
            if d == 1:
                self._period = 1
                self._cyclic_components_proj = np.zeros(self.n, dtype=int)
                return None

        self._period = d
        self._cyclic_components_proj = level % d
예제 #3
0
    def _compute_period(self):
        """
        Set ``self._period`` and ``self._cyclic_components_proj``.

        Use the algorithm described in:
        J. P. Jarvis and D. R. Shier,
        "Graph-Theoretic Analysis of Finite Markov Chains," 1996.

        """
        # Degenerate graph with a single node (which is strongly connected)
        # csgraph.reconstruct_path would raise an exception
        # github.com/scipy/scipy/issues/4018
        if self.n == 1:
            if self.csgraph[0, 0] == 0:  # No edge: "trivial graph"
                self._period = 1  # Any universally accepted definition?
                self._cyclic_components_proj = np.zeros(self.n, dtype=int)
                return None
            else:  # Self loop
                self._period = 1
                self._cyclic_components_proj = np.zeros(self.n, dtype=int)
                return None

        if not self.is_strongly_connected:
            raise NotImplementedError(
                'Not defined for a non strongly-connected digraph'
            )

        if np.any(self.csgraph.diagonal() > 0):
            self._period = 1
            self._cyclic_components_proj = np.zeros(self.n, dtype=int)
            return None

        # Construct a breadth-first search tree rooted at 0
        node_order, predecessors = \
            csgraph.breadth_first_order(self.csgraph, i_start=0)
        bfs_tree_csr = \
            csgraph.reconstruct_path(self.csgraph, predecessors)

        # Edges not belonging to tree_csr
        non_bfs_tree_csr = self.csgraph - bfs_tree_csr
        non_bfs_tree_csr.eliminate_zeros()

        # Distance to 0
        level = np.zeros(self.n, dtype=int)
        for i in range(1, self.n):
            level[node_order[i]] = level[predecessors[node_order[i]]] + 1

        # Determine the period
        d = 0
        for node_from, node_to in _csr_matrix_indices(non_bfs_tree_csr):
            value = level[node_from] - level[node_to] + 1
            d = gcd(d, value)
            if d == 1:
                self._period = 1
                self._cyclic_components_proj = np.zeros(self.n, dtype=int)
                return None

        self._period = d
        self._cyclic_components_proj = level % d
예제 #4
0
 def calculate_draw_order(self, parents):
     order = csgraph.depth_first_order(csgraph.reconstruct_path(
         self._edge_matrix, parents, directed=False),
                                       self.center,
                                       return_predecessors=False)[::-1]
     print('Order to Draw:')
     strf = ''
     for i in order:
         strf += str(self._images[i].name) + ', '
     print(strf)
     return order
예제 #5
0
 def _calculate_draw_order(self, parents):
     order = csgraph.depth_first_order(
         csgraph.reconstruct_path(self._edge_matrix,
                                  parents,
                                  directed=False),
         self.center,
         return_predecessors=False,
     )[::-1]
     log.info('Draw order: %s',
              ', '.join(self._images[i].name for i in order))
     return order
예제 #6
0
 def shortest_path_subtree(self, start_idx, directed=True):
   '''Returns a subgraph containing only the shortest paths from start_idx to
      every other vertex.
   '''
   adj = self.matrix()
   _, pred = ssc.dijkstra(adj, directed=directed, indices=start_idx,
                          return_predecessors=True)
   adj = ssc.reconstruct_path(adj, pred, directed=directed)
   if not directed:
     adj = adj + adj.T
   return self.__class__.from_adj_matrix(adj)
예제 #7
0
def digraph_to_DAG(forward_neighbors, cost, source=None):
    """
    Convert a directed graph to a DAG using a heuristic based on shortest path.
    
    This function first compute the shortest path tree. Then it iteratively add
    edges that were removed by the shortest path but which don't create cycles. 
    This is done following the order of the tree cumulative distance, thus 
    cycles are broken "after" the further away element.

    :Input:
        - forward_neighbors
            an NxK array of the neighbor indices of the K **forward** neighbors
            of N elements. 0 value neighbor not treated (dummy).
        - cost
            Array of (broadcastably) same shape as `neighbors` of their cost
             - All "real" neighbors cost must have **strictly** positive cost -
        - source
            boolean mask of the sources used by the shortest path algorithm
                ex1:   rgraph.segment.seed>0   ;   
                ex2:  -rgraph.segment.neighbors()[...,0].any(axis=1)
            if None, use the list of elements with no incomming edge.
            
    :Output:
        - a list of set (los) type of segment graph where
            los[i][io] is the set of incomming (`io=0`) or outgoing (`io=1`) 
            neighbors of segment `i`
        - list of removed edges (as pair-tuples)
        - the order of the graph traversal used to break cycles 
          (missing ids were unreachable, see note)
          
    :Note:
        Because it is based on a shortest path, all unreachable elements from 
        the given sources are not processed.
    
    :Warning:
      Behavior is not clear if their is an edge beween the same element (e,e)
      that is, a self looping root segment (does not append in practice)
      
    :todo:
      Add unbreakable edges, from initial axe path?
      output might not be a DAG anymore...
    """
    from scipy.sparse.csgraph import dijkstra, reconstruct_path

    nbor = forward_neighbors

    if source is None:
        source = _np.bincount(nbor.ravel(), minlength=nbor.shape[0]) == 0
        source[0] = True
    if _np.asarray(source).dtype == 'bool':
        source = source.nonzero()[0]

    # compute shortest path tree
    graph = neighbor_to_csgraph(nbor, value=cost)
    d, parent = dijkstra(graph, indices=source, return_predecessors=1)
    path_id = d.argmin(axis=0)
    parent = parent[path_id, _np.arange(d.shape[1])]
    tree = reconstruct_path(graph, parent)

    # get all edges that have been removed from the digraph
    rm_edge = [[] for node in xrange(tree.shape[0])]
    src, dst = (graph - tree).nonzero()  ## required cost >0
    for si, di in zip(src.tolist(), dst.tolist()):
        rm_edge[si].append(di)

    # get a topological order of the **tree**...
    #    s.t. parent[node_i] always appears before node_i
    d = d[path_id, _np.arange(d.shape[1])]
    dist_order = _np.argsort(d)
    if d.max() == _np.inf:
        dist_order = dist_order[:d[dist_order].argmax()]

    # The idea is to add iteratively ancestors following topological order:
    #   when processing an element, all its parents have already been processed
    # During this iteration, the algorithm adds removed edges when its starting
    # node has been processed. However it adds it only if this does not create
    # a cycle: if the second node is not an anscestor of the 1st
    added = []
    removed = []
    ancestors = [set() for node in xrange(tree.shape[0])]
    for e in dist_order:
        # add parent and its ancestors to e ancestors
        p = parent[e]
        if p >= 0:
            ancestors[e].update(ancestors[p])
            ancestors[e].add(p)

        # try to add removed edges starting at e
        for c in rm_edge[e]:
            if c not in ancestors[e]:
                ancestors[c].update(ancestors[e])
                ancestors[c].add(e)
                added.append((e, c))
            else:
                removed.append((e, c))

    # make a list-of-sets graph type, with shortest path tree and 'added' edges
    los = [(set(), set()) for node in xrange(tree.shape[0])]
    src, dst = tree.nonzero()
    for si, di in zip(src, dst):
        los[di][0].add(si)
        los[si][1].add(di)
    for si, di in added:
        los[di][0].add(si)
        los[si][1].add(di)

    return los, removed, dist_order