def graph_extract(s: int, g: Graph, pmap_vrelevant=None, pmap_erelevant=None, callback_vertex_extract=None, callback_edge_extract=None): """ Extract the edges of a given Graph according to an edge-based filtering starting from a given source node. Args: s: The VertexDescriptor of the source node. g: A Graph instance. pmap_vrelevant: A ReadPropertyMap{VertexDescriptor : bool} which indicates for each vertex whether if it must be duped or not. pmap_erelevant: A ReadPropertyMap{EdgeDescriptor : bool} which indicates each edge of the Graph with a boolean equal to True iff the edge is relevant. callback_vertex_extract: callback_edge_extract: """ if not pmap_vrelevant: pmap_vrelevant = make_func_property_map(lambda u: True) if not pmap_erelevant: pmap_erelevant = make_func_property_map(lambda e: True) map_vcolor = defaultdict(int) pmap_vcolor = make_assoc_property_map(map_vcolor) vis = DepthFirstSearchExtractVisitor(pmap_vrelevant, pmap_erelevant, pmap_vcolor, callback_vertex_extract, callback_edge_extract) depth_first_search(s, g, pmap_vcolor, vis, if_push=lambda e, g: pmap_erelevant[e] and pmap_vrelevant[target(e, g)])
def make_gdp2() -> GraphDp: g = make_g() vlabel = {u : "v%s" % u for u in vertices(g)} elabel = {e : "e%s%s" % (source(e, g), target(e, g)) for e in edges(g)} gdp = GraphDp( g, dpv = { "label" : make_assoc_property_map(vlabel), "color" : make_func_property_map(lambda q: "red" if q % 2 else "green"), }, dpe = { "label" : make_assoc_property_map(elabel), "color" : make_func_property_map(lambda e: "red" if target(e, g) % 2 else "green"), } ) return gdp
def depth_first_search( s: int, g: Graph, pmap_vcolor: ReadWritePropertyMap = None, vis: DefaultDepthFirstSearchVisitor = None, # N.B: The following parameters does not exist in libboost: if_push=None # if_push(e :EdgeDecriptor, g :Graph) -> bool returns True iff e is relevant ): if pmap_vcolor is None: map_vcolor = defaultdict(int) pmap_vcolor = make_assoc_property_map(map_vcolor) if vis is None: vis = DefaultDepthFirstSearchVisitor() if if_push is None: if_push = (lambda e, g: True) vis.start_vertex(s, g) pmap_vcolor[s] = GRAY vis.discover_vertex(s, g) u_edges = [e for e in out_edges(s, g) if if_push(e, g)] stack = deque([(s, 0, len(u_edges))]) while stack: # Pop the current vertex u. Its (i-1)-th first out-edges have already # been visited. The out-degree of u is equal to n. (u, i, n) = stack.pop() u_edges = [e for e in out_edges(u, g) if if_push(e, g)] while i != n: # e is the current edge. e = u_edges[i] v = target(e, g) vis.examine_edge(e, g) color_v = pmap_vcolor[v] # (color[v] == WHITE) means that v has not yet been visited. if color_v == WHITE: # u must be re-examined later, its i-th out-edge has been visited. vis.tree_edge(e, g) i += 1 stack.append((u, i, n)) # v becomes the new current vertex u = v pmap_vcolor[u] = GRAY vis.discover_vertex(u, g) u_edges = [e for e in out_edges(u, g) if if_push(e, g)] i = 0 n = len(u_edges) else: if color_v == GRAY: vis.back_edge(e, g) else: vis.forward_or_cross_edge(e, g) i += 1 # u and all the vertices reachable from u have been visited. pmap_vcolor[u] = BLACK vis.finish_vertex(u, g)
def examine_edge(self, e: EdgeDescriptor, g: Graph): """ Event triggered when the DFS discover a vertex not yet visited. Args: e: The EdgeDescriptor of the discovered vertex. g: The Graph. """ if self.m_pmap_erelevant[e] and self.m_pmap_vrelevant[target(e, g)]: self.examine_relevant_edge(e, g)
def graph_copy(s: int, g: Graph, g_dup: Graph, pmap_vrelevant: ReadPropertyMap = None, pmap_erelevant: ReadPropertyMap = None, pmap_vertices: ReadWritePropertyMap = None, pmap_edges: ReadWritePropertyMap = None, callback_dup_vertex=None, callback_dup_edge=None): """ Copy a sub-graph from a Graph according to an edge-based filtering starting from a given source node. Args: s: The VertexDescriptor of the source node. g: A Graph instance. pmap_vrelevant: A ReadPropertyMap{VertexDescriptor : bool} which indicates for each vertex whether if it must be duped or not. Only used if vis == None. pmap_erelevant: A ReadPropertyMap{EdgeDescriptor : bool} which indicates for each edge whether if it must be duped or not. Only used if vis == None. callback_dup_vertex: Callback(u, g, u_dup, g_dup). Pass None if irrelevant. callback_dup_edge: Callback(e, g, e_dup, g_dup). Pass None if irrelevant. vis: Pass a custom DepthFirstSearchExtractVisitor or None. This visitor must overload super()'s methods. """ # Prepare the needed mappings. map_vcolor = defaultdict(int) pmap_vcolor = make_assoc_property_map(map_vcolor) if not pmap_vrelevant: pmap_vrelevant = make_func_property_map(lambda u: True) if not pmap_erelevant: pmap_erelevant = make_func_property_map(lambda e: True) # Prepare the DepthFirstSearchCopyVisitor. if not pmap_vertices: map_vertices = dict() pmap_vertices = make_assoc_property_map(map_vertices) if not pmap_edges: map_edges = dict() pmap_edges = make_assoc_property_map(map_edges) vis = DepthFirstSearchCopyVisitor(g_dup, pmap_vrelevant, pmap_erelevant, pmap_vertices, pmap_edges, pmap_vcolor, callback_dup_vertex, callback_dup_edge) # Copy g to g_copy according to pmap_erelevant using a DFS from s. depth_first_search(s, g, pmap_vcolor, vis, if_push=lambda e, g: pmap_erelevant[e] and pmap_vrelevant[target(e, g)])
def examine_relevant_edge(self, e: EdgeDescriptor, g: Graph): u = source(e, g) v = target(e, g) u_dup = self.m_pmap_vertices[u] v_dup = self.m_pmap_vertices[ v] if v in self.m_dup_vertices else self.dup_vertex(v, g) (e_dup, _) = add_edge(u_dup, v_dup, self.m_g_dup) if self.m_pmap_edges: self.m_pmap_edges[e] = e_dup if self.m_callback_dup_edge: self.m_callback_dup_edge(e, g, e_dup, self.m_g_dup)
def finish_vertex(self, u: int, g: DirectedGraph): for e in out_edges(u, g): v = target(e, g) if self.m_pmap_component[v] == INFINITY: # u is attached to the "lowest" root among the root of u and v self.m_pmap_root[u] = self.discover_min( self.m_pmap_root[u], self.m_pmap_root[v]) if self.m_pmap_root[u] == u: # The vertices stacked since u belong to the same component of u. while True: v = self.m_stack.popleft() self.m_pmap_component[v] = self.total self.m_pmap_root[v] = u if u == v: break self.m_total += 1
def edge_color(e, g, pmap_component, pmap_color, default_color = "black"): """ Returns the color assigned to an edge. Args: e: An EdgeDescriptor. g: A DirectedGraph. pmap_component: A ReadPropertyMap, mapping a vertex with its strongly connected component. pmap_color: A ReadPropertyMap, mapping a component ID with a color. default_color: color returned if the color of the source and the target of e mismatch. """ u = source(e, g) v = target(e, g) color_u = pmap_color[pmap_component[u]] color_v = pmap_color[pmap_component[v]] return color_u if color_u == color_v else default_color
def dijkstra_shortest_paths_iteration( heap: Heap, g: DirectedGraph, pmap_eweight: ReadPropertyMap, pmap_vpreds: ReadWritePropertyMap, pmap_vdist: ReadWritePropertyMap, pmap_vcolor: ReadWritePropertyMap, compare: BinaryPredicate = Less(), # Ignored, see Heap class. combine: BinaryFunction = ClosedPlus(), vis: DijkstraVisitor = DijkstraVisitor()): if vis is None: vis = DijkstraVisitor() u = heap.pop() w_su = pmap_vdist[u] vis.examine_vertex(u, g) # Update weight and predecessors of each successor of u for e in out_edges(u, g): vis.examine_edge(e, g) v = target(e, g) w_sv = pmap_vdist[v] w_uv = pmap_eweight[e] w = combine(w_su, w_uv) if compare(w, w_sv): # Traversing u is worth! pmap_vdist[v] = w pmap_vpreds[v] = {e} if pmap_vcolor[v] == WHITE: heap.push(v) # As v is WHITE, v cannot be in the heap. pmap_vcolor[v] = GRAY vis.discover_vertex(v, g) elif pmap_vcolor[v] == GRAY: heap.decrease_key(v) vis.edge_relaxed(e, g) elif w == w_sv: # Hence we discover equally-cost shortest paths preds_v = pmap_vpreds[v] preds_v.add(e) pmap_vpreds[v] = preds_v vis.edge_relaxed(e, g) else: vis.edge_not_relaxed(e, g) pmap_vcolor[u] = BLACK vis.finish_vertex(u, g) return w_su
def cut(s: int, g: Graph, in_cut) -> set: """ Find a vertex cut given an edge cut. Args: g: A `Graph` instance corresponding to an acyclic graph. s: The `VertexDescriptor` corresponding to the source vertex. in_cut: `Callback(EdgeDescriptor, Graph) -> bool` indicating whether an edge belong to the considered cut. """ class LeavesVisitor(DefaultDepthFirstSearchVisitor): def __init__(self, leaves: set): self.leaves = leaves def examine_edge(self, e: EdgeDescriptor, g: Graph): u = source(e, g) self.leaves.discard(u) def discover_vertex(self, u: int, g: Graph): self.leaves.add(u) class IfPush: def __init__(self, in_cut, cutting_edges: set): self.in_cut = in_cut self.cutting_edges = cutting_edges def __call__(self, e: EdgeDescriptor, g: Graph) -> bool: is_cutting_edge = self.in_cut(e, g) if is_cutting_edge: self.cutting_edges.add(e) return not is_cutting_edge leaves = set() cutting_edges = set() map_vcolor = defaultdict(int) depth_first_search(s, g, pmap_vcolor=make_assoc_property_map(map_vcolor), vis=LeavesVisitor(leaves), if_push=IfPush(in_cut, cutting_edges)) return {target(e, g) for e in cutting_edges } | {u for u in leaves} - {source(e, g) for e in cutting_edges}
def breadth_first_search_graph( g: Graph, sources: set = None, # Or a generator e.g. vertices(g) pmap_vcolor: ReadWritePropertyMap = None, vis=None, # N.B: The following parameter does not exist in libboost: if_push=None # if_push(e :EdgeDecriptor, g :Graph) -> bool returns True iff e is relevant ): if pmap_vcolor is None: map_vcolor = defaultdict(int) pmap_vcolor = make_assoc_property_map(map_vcolor) if vis is None: vis = DefaultBreadthFirstSearchVisitor() if not if_push: if_push = (lambda e, g: True) stack = deque() for s in sources: pmap_vcolor[s] = GRAY vis.discover_vertex(s, g) stack.append(s) while stack: u = stack.pop() vis.examine_vertex(u, g) for e in out_edges(u, g): if not if_push(e, g): continue v = target(e, g) vis.examine_edge(e, g) color_v = pmap_vcolor[v] if color_v == WHITE: vis.tree_edge(e, g) pmap_vcolor[v] = GRAY vis.discover_vertex(v, g) stack.appendleft(v) elif color_v == GRAY: vis.gray_target(e, g) else: vis.black_target(e, g) pmap_vcolor[u] = BLACK vis.finish_vertex(u, g)
def test_directed_graph(links: list = LINKS): map_eweight = dict() pmap_eweight = make_assoc_property_map(map_eweight) g = make_graph(LINKS, pmap_eweight, directed=True, build_reverse_edge=False) map_vpreds = defaultdict(set) map_vdist = dict() dijkstra_shortest_paths(g, 0, pmap_eweight, make_assoc_property_map(map_vpreds), make_assoc_property_map(map_vdist)) infty = sys.maxsize E = {(source(e, g), target(e, g)): e for e in edges(g)} assert map_vpreds == { 1: {E[0, 1]}, 2: {E[1, 2]}, 3: {E[1, 3]}, 4: {E[3, 4]}, 5: {E[4, 5]}, 6: {E[4, 6]}, 7: {E[6, 7]}, 8: {E[7, 8]}, 9: {E[7, 9]} } assert map_vdist == { 0: 0, 1: 1, 2: 2, 3: 4, 4: 5, 5: 6, 6: 6, 7: 14, 8: 15, 9: 15, 10: infty }
def make_path(g: DirectedGraph, s: int, t: int, pmap_vpreds: ReadPropertyMap) -> list: """ Extract the path from `s` to `t` in a graph `g` gien to `t` in a graph `g` given the predecessors map computed using `dijkstra_shortest_paths` from `s`. Args: g: The graph. s: The target's vertex descriptor. t: The target's vertex descriptor. pmap_vpreds: A `ReadPropertyMap{int : set(int)}` mapping each vertex with its set of (direct) predecessors. Returns: A list of consecutive arcs forming a shortest path from `s` of `t`. If several shortest paths exist from `s` to `t`, this function returns an arbitrary path. """ arcs = make_dag(g, s, t, pmap_vpreds, single_path=True) path = list(arcs) path.sort(key=lambda e: (source(e, g), target(e, g))) return path
def edge_to_pair(e: EdgeDescriptor, g: DirectedGraph) -> tuple: return (source(e, g), target(e, g))
def test_graph_copy_small(threshold :int = 50): g = DirectedGraph(5) (e01, _) = add_edge(0, 1, g) (e02, _) = add_edge(0, 2, g) (e04, _) = add_edge(0, 4, g) (e12, _) = add_edge(1, 2, g) (e23, _) = add_edge(2, 3, g) (e24, _) = add_edge(2, 4, g) (e40, _) = add_edge(4, 0, g) (e44, _) = add_edge(4, 4, g) map_eweight = { e01 : 83, e02 : 3, e04 : 78, e12 : 92, e23 : 7, e24 : 18, e40 : 51, e44 : 84, } pmap_eweight = make_assoc_property_map(map_eweight) pmap_erelevant = make_func_property_map(lambda e: pmap_eweight[e] >= threshold) g_dup = DirectedGraph(0) # Edge duplicate map_eweight_dup = dict() pmap_eweight_dup = make_assoc_property_map(map_eweight_dup) def callback_dup_edge(e, g, e_dup, g_dup): pmap_eweight_dup[e_dup] = pmap_eweight[e] # Vertex mapping map_vertices = dict() pmap_vertices = make_assoc_property_map(map_vertices) map_edges = dict() pmap_edges = make_assoc_property_map(map_edges) graph_copy( 0, g, g_dup, pmap_erelevant = pmap_erelevant, pmap_vertices = pmap_vertices, pmap_edges = pmap_edges, callback_dup_edge = callback_dup_edge ) if in_ipynb(): ori_html = dotstr_to_html( GraphDp( g, dpv = { "label" : make_func_property_map(lambda u: "%s<br/>(becomes %s)" % (u, pmap_vertices[u])) }, dpe = { "color" : make_func_property_map(lambda e : "darkgreen" if pmap_erelevant[e] else "lightgrey"), "style" : make_func_property_map(lambda e : "solid" if pmap_erelevant[e] else "dashed"), "label" : pmap_eweight, } ).to_dot() ) dup_html = dotstr_to_html( GraphDp( g_dup, dpe = { "label" : pmap_eweight_dup, } ).to_dot() ) html( """ <table> <tr> <th>Original</th> <th>Extracted</th> </tr><tr> <td>%s</td> <td>%s</td> </tr> </table> """ % (ori_html, dup_html) ) if threshold == 50: expected_num_edges = 5 assert map_vertices == { 0 : 0, 1 : 1, 2 : 2, 4 : 3 } for e, e_dup in map_edges.items(): u = source(e, g) v = target(e, g) u_dup = source(e_dup, g_dup) v_dup = target(e_dup, g_dup) assert u_dup == pmap_vertices[u], "u_dup = %s ; pmap_vertices[%s] = %s" % (u_dup, u, pmap_vertices[u]) assert v_dup == pmap_vertices[v], "v_dup = %s ; pmap_vertices[%s] = %s" % (v_dup, v, pmap_vertices[v]) assert pmap_eweight[e] == pmap_eweight_dup[e_dup] elif threshold < min([w for w in map_eweight.values()]): expected_num_edges = num_edges(g) elif threshold > max([w for w in map_eweight.values()]): expected_num_edges = 0 assert expected_num_edges == num_edges(g_dup), \ """ Invalid edge number: Expected: %s Obtained: %s """ % (expected_num_edges, num_edges(g_dup))
def dijkstra_shortest_paths(g: DirectedGraph, s: int, pmap_eweight: ReadPropertyMap, pmap_vpreds: ReadWritePropertyMap, pmap_vdist: ReadWritePropertyMap, compare: BinaryPredicate = Less(), combine: BinaryFunction = ClosedPlus(), zero: int = 0, infty: int = sys.maxsize, vis: DijkstraVisitor = DijkstraVisitor()): """ Compute the shortest path in a graph from a given source node. Args: g: A DirectedGraph instance. s: The vertex descriptor of the source node. pmap_eweight: A ReadPropertyMap{EdgeDescriptor : Distance} which map each edge with its weight. pmap_vpreds: A ReadWritePropertyMap{VertexDescriptor : EdgeDescriptor} which will map each vertex with its incident arcs in the shortest path Directed Acyclic Graph. pmap_vdist: A ReadWritePropertyMap{VertexDescriptor : Distance} which will map each vertex with the weight of its shortest path(s) from s. zero: The null Distance (e.g. 0). infty: The infinite Distance (e.g. sys.maxsize). vis: A DijkstraVisitor instance. Example: g = DirectedGraph(2) e, _ = add_edge(0, 1, g) map_eweight[e] = 10 map_vpreds = defaultdict(set) map_vdist = dict() dijkstra_shortest_paths( g, u, make_assoc_property_map(map_eweight), make_assoc_property_map(map_vpreds), make_assoc_property_map(map_vdist) ) """ # WHITE: not yet processed, GRAY: under process, BLACK: processed. color = defaultdict(int) pmap_vcolor = make_assoc_property_map(color) pmap_vcolor[s] = WHITE for u in vertices(g): pmap_vdist[u] = zero if u == s else infty #pmap_vpreds[u] = set() #pmap_vcolor[u] = GRAY if u == s else WHITE stack = {s} while stack: u = min(stack, key=lambda u: pmap_vdist[u]) # TODO use compare stack.remove(u) w_su = pmap_vdist[u] # Update weight and predecessors of each successor of u for e in out_edges(u, g): v = target(e, g) w_sv = pmap_vdist[v] w_uv = pmap_eweight[e] w = combine(w_su, w_uv) if compare(w, w_sv): # Traversing u is worth! pmap_vdist[v] = w pmap_vpreds[v] = {e} if pmap_vcolor[v] == WHITE: pmap_vcolor[v] = GRAY stack.add(v) elif w == w_sv: # Hence we discover equally-cost shortest paths preds_v = pmap_vpreds[v] preds_v.add(e) pmap_vpreds[v] = preds_v pmap_vcolor[u] = BLACK