Source code for utool.util_graph

# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals
import numpy as np
from utool import util_inject
(print, rrr, profile) = util_inject.inject2(__name__, '[depgraph_helpers]')


[docs]def testdata_graph(): r""" Returns: tuple: (graph, G) CommandLine: python -m utool.util_graph --exec-testdata_graph --show Example: >>> # DISABLE_DOCTEST >>> from utool.util_graph import * # NOQA >>> import utool as ut >>> (graph, G) = testdata_graph() >>> import plottool as pt >>> ut.ensure_pylab_qt4() >>> pt.show_nx(G, layout='pygraphviz') >>> ut.show_if_requested() """ import networkx as nx import utool as ut # Define adjacency list graph = { 'a': ['b'], 'b': ['c', 'f', 'e'], 'c': ['g', 'd'], 'd': ['c', 'h'], 'e': ['a', 'f'], 'f': ['g'], 'g': ['f'], 'h': ['g', 'd'], 'i': ['j'], 'j': [], } graph = { 'a': ['b'], 'b': ['c'], 'c': ['d'], 'd': ['a', 'e'], 'e': ['c'], } #graph = {'a': ['b'], 'b': ['c'], 'c': ['d'], 'd': ['a']} #graph = {'a': ['b'], 'b': ['c'], 'c': ['d'], 'd': ['e'], 'e': ['a']} graph = {'a': ['b'], 'b': ['c'], 'c': ['d'], 'd': ['e'], 'e': ['a'], 'f': ['c']} #graph = {'a': ['b'], 'b': ['c'], 'c': ['d'], 'd': ['e'], 'e': ['b']} graph = {'a': ['b', 'c', 'd'], 'e': ['d'], 'f': ['d', 'e'], 'b': [], 'c': [], 'd': []} # double pair in non-scc graph = {'a': ['b', 'c', 'd'], 'e': ['d'], 'f': ['d', 'e'], 'b': [], 'c': [], 'd': ['e']} # double pair in non-scc #graph = {'a': ['b', 'c', 'd'], 'e': ['d', 'f'], 'f': ['d', 'e'], 'b': [], 'c': [], 'd': ['e']} # double pair in non-scc #graph = {'a': ['b', 'c', 'd'], 'e': ['d', 'c'], 'f': ['d', 'e'], 'b': ['e'], 'c': ['e'], 'd': ['e']} # double pair in non-scc graph = {'a': ['b', 'c', 'd'], 'e': ['d', 'c'], 'f': ['d', 'e'], 'b': ['e'], 'c': ['e', 'b'], 'd': ['e']} # double pair in non-scc # Extract G = (V, E) nodes = list(graph.keys()) edges = ut.flatten([[(v1, v2) for v2 in v2s] for v1, v2s in graph.items()]) G = nx.DiGraph() G.add_nodes_from(nodes) G.add_edges_from(edges) if False: G.remove_node('e') del graph['e'] for val in graph.values(): try: val.remove('e') except ValueError: pass #ut.ensure_pylab_qt4() #pt.show_nx(G, layout='pygraphviz') return graph, G
[docs]def dfs_template(graph, previsit, postvisit): seen_ = set() meta = {} # NOQA def previsit(parent): pass def postvisit(parent): pass def explore(graph, parent, seen_): # Mark visited seen_.add(parent) previsit(parent) # Explore children children = graph[parent] for child in children: if child not in seen_: explore(graph, child, seen_) postvisit(parent) # Run Depth First Search for node in graph.keys(): if node not in seen_: explore(graph, node, seen_)
[docs]def topsort_dfs(G, previsit, postvisit): Gr = G.reverse() graph = dict(zip(Gr.nodes(), Gr.adjacency_list())) seen_ = set() meta = { 'clock': 0, 'pre': {}, 'post': {}, } def previsit(parent): meta['pre'][parent] = meta['clock'] meta['clock'] += 1 def postvisit(parent): meta['post'][parent] = meta['clock'] meta['clock'] += 1 def explore(graph, parent, seen_): # Mark visited seen_.add(parent) previsit(parent) # Explore children children = graph[parent] for child in children: if child not in seen_: explore(graph, child, seen_) postvisit(parent) # Run Depth First Search nodes = graph.keys() import numpy as np np.random.shuffle(nodes) for node in nodes: if node not in seen_: explore(graph, node, seen_) #for node in nodes: # postorder = list(nx.dfs_postorder_nodes(Gr, node)) top_sort_order = ut.sortedby(meta['post'].keys(), meta['post'].values()) print('top_sort_order = %r' % (top_sort_order,)) graph = dict(zip(G.nodes(), G.adjacency_list())) seen_ = set() meta = { 'clock': 0, 'pre': {}, 'post': {}, } def previsit2(parent): meta['pre'][parent] = meta['clock'] meta['clock'] += 1 def postvisit2(parent): meta['post'][parent] = meta['clock'] meta['clock'] += 1 def explore(graph, parent, seen_): # Mark visited seen_.add(parent) previsit2(parent) # Explore children children = graph[parent] for child in children: if child not in seen_: explore(graph, child, seen_) postvisit2(parent) # Run Depth First Search strongly_connected_compoments = [] for node in reversed(top_sort_order): if node not in seen_: before = seen_.copy() explore(graph, node, seen_) strongly_connected_compoments.append(seen_ - before)
[docs]def find_odd_cycle(): r""" given any starting point in an scc if there is an odd length cycle in the scc then the starting node is part of the odd length cycle Let s* be part of the odd length cycle Start from any point s There is also a cycle from (s* to s) due to scc. If that cycle is even, then go to s*, then go in the odd length cycle back to s* and then go back to s, which makes this path odd. If s s* s is odd we are done. because it is strongly connected there is a path from s* to s and s to s*. If that cycle is odd then done otherwise, # Run pairity check on each scc # Then check all edges for equal pairity CommandLine: python -m utool.util_graph --exec-find_odd_cycle --show Example: >>> # DISABLE_DOCTEST >>> from utool.util_graph import * # NOQA >>> import utool as ut >>> result = find_odd_cycle() >>> print(result) >>> ut.show_if_requested() """ import utool as ut graph, G = testdata_graph() seen_ = set() meta = { 'clock': 0, 'pre': {}, 'post': {}, 'pairity': {n: 0 for n in graph} } def previsit(parent): meta['pre'][parent] = meta['clock'] meta['clock'] += 1 def postvisit(parent): meta['post'][parent] = meta['clock'] meta['clock'] += 1 def explore(graph, parent, seen_): # Mark visited seen_.add(parent) previsit(parent) # Explore children children = graph[parent] for child in children: if child not in seen_: meta['pairity'][child] = 1 - meta['pairity'][parent] explore(graph, child, seen_) postvisit(parent) # Run Depth First Search for node in graph.keys(): if node not in seen_: explore(graph, node, seen_) # Check edges for neighboring pairities import networkx as nx scc_list = list(nx.strongly_connected_components(G)) found = False for scc in scc_list: SCC_G = nx.subgraph(G, scc) for u, v in SCC_G.edges(): if meta['pairity'][u] == meta['pairity'][v]: found = True print('FOUND ODD CYCLE') if not found: print("NO ODD CYCLES") # Mark edge types edge_types = {(0, 1, 3, 2): 'forward', (1, 0, 2, 3): 'back', (1, 3, 0, 2): 'cross', } edge_labels = {} type_to_edges = ut.ddict(list) pre = meta['pre'] post = meta['post'] for u, v in G.edges(): orders = [pre[u], pre[v], post[u], post[v]] sortx = tuple(ut.argsort(orders)) type_ = edge_types[sortx] edge_labels[(u, v)] = type_ type_to_edges[type_].append((u, v)) ## Check back edges #is_odd_list = [] #for back_edge in type_to_edges['back']: # u, v = back_edge # pre_v = meta['pre'][v] # post_u = meta['post'][u] # is_even = (post_u - pre_v) % 2 == 0 # is_odd = not is_even # is_odd_list.append(is_odd) # Visualize the graph node_labels = { #node: (meta['pre'][node], meta['post'][node]) node: (meta['pairity'][node]) for node in graph } import networkx as nx import plottool as pt scc_list = list(nx.strongly_connected_components(G)) node_colors = {node: color for scc, color in zip(scc_list, pt.distinct_colors(len(scc_list))) for node in scc} nx.set_node_attributes(G, 'label', node_labels) nx.set_node_attributes(G, 'color', node_colors) #nx.set_edge_attributes(G, 'label', edge_labels) ut.ensure_pylab_qt4() #pt.figure(pt.next_fnum()) pt.show_nx(G, layout='pygraphviz') #dfs(G)
[docs]def dict_depth(dict_, accum=0): if not isinstance(dict_, dict): return accum return max([dict_depth(val, accum + 1) for key, val in dict_.items()])
[docs]def edges_to_adjacency_list(edges): import utool as ut children_, parents_ = list(zip(*edges)) parent_to_children = ut.group_items(parents_, children_) #to_leafs = {tablename: path_to_leafs(tablename, parent_to_children)} return parent_to_children
[docs]def get_ancestor_levels(graph, tablename): import networkx as nx import utool as ut root = nx.topological_sort(graph)[0] reverse_edges = [(e2, e1) for e1, e2 in graph.edges()] child_to_parents = ut.edges_to_adjacency_list(reverse_edges) to_root = ut.paths_to_root(tablename, root, child_to_parents) from_root = ut.reverse_path(to_root, root, child_to_parents) ancestor_levels_ = ut.get_levels(from_root) ancestor_levels = ut.longest_levels(ancestor_levels_) return ancestor_levels
[docs]def get_descendant_levels(graph, tablename): #import networkx as nx import utool as ut parent_to_children = ut.edges_to_adjacency_list(graph.edges()) to_leafs = ut.path_to_leafs(tablename, parent_to_children) descendant_levels_ = ut.get_levels(to_leafs) descendant_levels = ut.longest_levels(descendant_levels_) return descendant_levels
[docs]def paths_to_root(tablename, root, child_to_parents): """ CommandLine: python -m utool.util_graph --exec-paths_to_root:0 python -m utool.util_graph --exec-paths_to_root:1 Example: >>> # ENABLE_DOCTEST >>> from utool.util_graph import * # NOQA >>> import utool as ut >>> child_to_parents = { >>> 'chip': ['dummy_annot'], >>> 'chipmask': ['dummy_annot'], >>> 'descriptor': ['keypoint'], >>> 'fgweight': ['keypoint', 'probchip'], >>> 'keypoint': ['chip'], >>> 'notch': ['dummy_annot'], >>> 'probchip': ['dummy_annot'], >>> 'spam': ['fgweight', 'chip', 'keypoint'] >>> } >>> root = 'dummy_annot' >>> tablename = 'fgweight' >>> to_root = paths_to_root(tablename, root, child_to_parents) >>> result = ut.repr3(to_root) >>> print(result) { 'keypoint': { 'chip': { 'dummy_annot': None, }, }, 'probchip': { 'dummy_annot': None, }, } Example: >>> from utool.util_graph import * # NOQA >>> import utool as ut >>> root = u'annotations' >>> tablename = u'Notch_Tips' >>> child_to_parents = { >>> 'Block_Curvature': [ >>> 'Trailing_Edge', >>> ], >>> 'Has_Notch': [ >>> 'annotations', >>> ], >>> 'Notch_Tips': [ >>> 'annotations', >>> ], >>> 'Trailing_Edge': [ >>> 'Notch_Tips', >>> ], >>> } >>> to_root = paths_to_root(tablename, root, child_to_parents) >>> result = ut.repr3(to_root) >>> print(result) """ if tablename == root: return None parents = child_to_parents[tablename] return {parent: paths_to_root(parent, root, child_to_parents) for parent in parents}
[docs]def path_to_leafs(tablename, parent_to_children): children = parent_to_children[tablename] if len(children) == 0: return None return {child: path_to_leafs(child, parent_to_children) for child in children}
[docs]def get_allkeys(dict_): import utool as ut if not isinstance(dict_, dict): return [] subkeys = [[key] + get_allkeys(val) for key, val in dict_.items()] return ut.unique_ordered(ut.flatten(subkeys))
[docs]def traverse_path(start, end, seen_, allkeys, mat): import utool as ut if seen_ is None: seen_ = set([]) index = allkeys.index(start) sub_indexes = np.where(mat[index])[0] if len(sub_indexes) > 0: subkeys = ut.take(allkeys, sub_indexes) # subkeys_ = ut.take(allkeys, sub_indexes) # subkeys = [subkey for subkey in subkeys_ # if subkey not in seen_] # for sk in subkeys: # seen_.add(sk) if len(subkeys) > 0: return {subkey: traverse_path(subkey, end, seen_, allkeys, mat) for subkey in subkeys} return None
[docs]def reverse_path(dict_, root, child_to_parents): """ CommandLine: python -m utool.util_graph --exec-reverse_path --show Example: >>> # ENABLE_DOCTEST >>> from utool.util_graph import * # NOQA >>> import utool as ut >>> child_to_parents = { >>> 'chip': ['dummy_annot'], >>> 'chipmask': ['dummy_annot'], >>> 'descriptor': ['keypoint'], >>> 'fgweight': ['keypoint', 'probchip'], >>> 'keypoint': ['chip'], >>> 'notch': ['dummy_annot'], >>> 'probchip': ['dummy_annot'], >>> 'spam': ['fgweight', 'chip', 'keypoint'] >>> } >>> to_root = { >>> 'fgweight': { >>> 'keypoint': { >>> 'chip': { >>> 'dummy_annot': None, >>> }, >>> }, >>> 'probchip': { >>> 'dummy_annot': None, >>> }, >>> }, >>> } >>> reversed_ = reverse_path(to_root, 'dummy_annot', child_to_parents) >>> result = ut.repr3(reversed_) >>> print(result) { 'dummy_annot': { 'chip': { 'keypoint': { 'fgweight': None, }, }, 'probchip': { 'fgweight': None, }, }, } """ # Hacky but illustrative # TODO; implement non-hacky version allkeys = get_allkeys(dict_) mat = np.zeros((len(allkeys), len(allkeys))) for key in allkeys: if key != root: for parent in child_to_parents[key]: rx = allkeys.index(parent) cx = allkeys.index(key) mat[rx][cx] = 1 end = None seen_ = set([]) reversed_ = {root: traverse_path(root, end, seen_, allkeys, mat)} return reversed_
[docs]def get_levels(dict_, n=0, levels=None): r""" Args: dict_ (dict_): a dictionary n (int): (default = 0) levels (None): (default = None) CommandLine: python -m utool.util_graph --test-get_levels --show python3 -m utool.util_graph --test-get_levels --show Example: >>> # ENABLE_DOCTEST >>> from utool.util_graph import * # NOQA >>> import utool as ut >>> from_root = { >>> 'dummy_annot': { >>> 'chip': { >>> 'keypoint': { >>> 'fgweight': None, >>> }, >>> }, >>> 'probchip': { >>> 'fgweight': None, >>> }, >>> }, >>> } >>> dict_ = from_root >>> n = 0 >>> levels = None >>> levels_ = get_levels(dict_, n, levels) >>> result = ut.repr2(levels_, nl=1) >>> print(result) [ ['dummy_annot'], ['chip', 'probchip'], ['keypoint', 'fgweight'], ['fgweight'], ] """ if levels is None: levels_ = [[] for _ in range(dict_depth(dict_))] else: levels_ = levels if dict_ is None: return [] for key in dict_.keys(): levels_[n].append(key) for val in dict_.values(): get_levels(val, n + 1, levels_) return levels_
[docs]def longest_levels(levels_): r""" Args: levels_ (list): CommandLine: python -m utool.util_graph --exec-longest_levels --show Example: >>> # ENABLE_DOCTEST >>> from utool.util_graph import * # NOQA >>> import utool as ut >>> levels_ = [ >>> ['dummy_annot'], >>> ['chip', 'probchip'], >>> ['keypoint', 'fgweight'], >>> ['fgweight'], >>> ] >>> new_levels = longest_levels(levels_) >>> result = ('new_levels = %s' % (ut.repr2(new_levels, nl=1),)) >>> print(result) new_levels = [ ['dummy_annot'], ['chip', 'probchip'], ['keypoint'], ['fgweight'], ] """ return shortest_levels(levels_[::-1])[::-1] # seen_ = set([]) # new_levels = [] # for level in levels_[::-1]: # new_level = [item for item in level if item not in seen_] # seen_ = seen_.union(set(new_level)) # new_levels.append(new_level) # new_levels = new_levels[::-1] # return new_levels
[docs]def shortest_levels(levels_): r""" Args: levels_ (list): CommandLine: python -m utool.util_graph --exec-shortest_levels --show Example: >>> # ENABLE_DOCTEST >>> from utool.util_graph import * # NOQA >>> import utool as ut >>> levels_ = [ >>> ['dummy_annot'], >>> ['chip', 'probchip'], >>> ['keypoint', 'fgweight'], >>> ['fgweight'], >>> ] >>> new_levels = shortest_levels(levels_) >>> result = ('new_levels = %s' % (ut.repr2(new_levels, nl=1),)) >>> print(result) new_levels = [ ['dummy_annot'], ['chip', 'probchip'], ['keypoint', 'fgweight'], ] """ seen_ = set([]) new_levels = [] for level in levels_: new_level = [item for item in level if item not in seen_] seen_ = seen_.union(set(new_level)) if len(new_level) > 0: new_levels.append(new_level) new_levels = new_levels return new_levels
if __name__ == '__main__': r""" CommandLine: python -m utool.util_graph --allexamples """ import multiprocessing multiprocessing.freeze_support() # for win32 import utool as ut # NOQA ut.doctest_funcs()