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]
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
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
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
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)
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