def from_abstract_graph(cls, g_: AbstractGraph): "Obtain base graph from AbstractGraph implementing object" if issubclass(g_, AbstractGraph): raise TypeError("Argument must implement AbstractGraph interface") nodes = set(g_.V) edges = set(g_.E) data = g_.data() gid = g_.id() return BaseGraph(gid=gid, data=data, nodes=nodes, edges=edges)
def is_stable(g: AbstractGraph, ns: FrozenSet[AbstractNode]) -> bool: """! \brief check if given node set is stable We ensure that no nodes in the given node set is a neighbour of one another as per the definition of Diestel 2017, p. 3. \throws ValueError if argument node set is not a subset of vertices of the graph \code{.py} >>> n1 = Node("n1", {}) >>> n2 = Node("n2", {}) >>> n3 = Node("n3", {}) >>> n4 = Node("n4", {}) >>> n5 = Node("n5", {}) >>> e1 = Edge( >>> "e1", start_node=n1, end_node=n2, edge_type=EdgeType.UNDIRECTED >>> ) >>> e2 = Edge( >>> "e2", start_node=n2, end_node=n3, edge_type=EdgeType.UNDIRECTED >>> ) >>> e3 = Edge( >>> "e3", start_node=n3, end_node=n4, edge_type=EdgeType.UNDIRECTED >>> ) >>> graph_2 = Graph( >>> "g2", >>> data={"my": "graph", "data": "is", "very": "awesome"}, >>> nodes=set([n1, n2, n3, n4, n5]), >>> edges=set([e1, e2, e3]), >>> ) >>> graph_2.is_stable(set([n1, n3, n5])) >>> True \endcode """ if ns.issubset(BaseGraphOps.nodes(g)) is False: raise ValueError("node set is not contained in graph") node_list = list(ns) while node_list: n1 = node_list.pop() for n2 in node_list: if g.is_neighbour_of(n1=n1, n2=n2): return False return True
def set_op( g: AbstractGraph, obj: Union[Set[AbstractNode], Set[AbstractEdge], AbstractGraph, AbstractNode, AbstractEdge, ], op: Callable[[Union[Set[AbstractNode], Set[AbstractEdge]]], Union[Set[AbstractNode], Set[AbstractEdge], AbstractGraph], ], ) -> Optional[Union[Set[AbstractNode], Set[AbstractEdge], bool]]: """! \brief generic set operation for graph \param obj the hooked object to operation. We deduce its corresponding argument from its type. \param op operation that is going to be applied to obj and its corresponding object. The idea is to give a single interface for generic set operation functions. For example if object is a set of nodes we provide the target for the operation as the nodes of this graph, if it is an edge we provide a set of edges of this graph """ is_node = isinstance(obj, AbstractNode) if is_node: return BaseGraphSetOps.set_op_node_edge(g=g, obj=set([obj]), op=op) is_edge = isinstance(obj, AbstractEdge) if is_edge: return BaseGraphSetOps.set_op_node_edge(g=g, obj=set([obj]), op=op) is_set = isinstance(obj, (set, frozenset)) if is_set: return BaseGraphSetOps.set_op_node_edge(g=g, obj=obj, op=op) is_graph = isinstance(obj, AbstractGraph) if is_graph: oeset = BaseGraphOps.edges(obj) onset = BaseGraphOps.nodes(obj) oedge_set = BaseGraphSetOps.set_op(g, obj=oeset, op=op) onode_set = BaseGraphSetOps.set_op(g, obj=onset, op=op) gdata = g.data() gdata.update(obj.data()) return BaseGraph(gid=str(uuid4()), nodes=onode_set, edges=oedge_set, data=gdata) else: raise TypeError("argument type is not supported: " + type(obj).__name__)
def neighbours_of(g: AbstractGraph, n1: AbstractEdge) -> Set[AbstractNode]: """! \brief obtain neighbour set of a given node. \param n1 the node whose neighbour set we are searching for \throws ValueError if node is not inside the graph \code{.py} >>> n1 = Node("n1", {}) >>> n2 = Node("n2", {}) >>> n3 = Node("n3", {}) >>> n4 = Node("n4", {}) >>> e1 = Edge( >>> "e1", start_node=n1, end_node=n2, edge_type=EdgeType.UNDIRECTED >>> ) >>> e2 = Edge( >>> "e2", start_node=n2, end_node=n3, edge_type=EdgeType.UNDIRECTED >>> ) >>> e3 = Edge( >>> "e3", start_node=n3, end_node=n4, edge_type=EdgeType.UNDIRECTED >>> ) >>> graph_2 = Graph( >>> "g2", >>> data={"my": "graph", "data": "is", "very": "awesome"}, >>> nodes=set([n1, n2, n3, n4]), >>> edges=set([e1, e2, e3]), >>> ) >>> neighbours = graph_2.neighbours_of(n2) >>> [n.id() for n in neighbours] >>> ["n1", "n3"] \endcode """ if not BaseGraphOps.is_in(g, n1): raise ValueError("node is not in graph") neighbours = set() for n2 in BaseGraphOps.nodes(g): if g.is_neighbour_of(n1=n1, n2=n2) is True: neighbours.add(n2) return neighbours
def breadth_first_search( g: AbstractGraph, n1: AbstractNode, edge_generator: Callable[[AbstractNode], Set[AbstractEdge]], ) -> BaseGraphBFSResult: """! \brief find shortest path from given node to all other nodes Applies the Breadth first search algorithm from Even and Guy Even 2012, p. 12 \throws ValueError if given node is not found in graph instance """ if not BaseGraphOps.is_in(g, n1): raise ValueError("argument node is not in graph") nid = n1.id() Q = [nid] V: Dict[str, AbstractNode] = {v.id(): v for v in g.V} l_vs = {v: math.inf for v in V} l_vs[nid] = 0 T = set([nid]) P: Dict[str, Dict[str, str]] = {} P[nid] = {} while Q: u = Q.pop(0) unode = V[u] for edge in edge_generator(unode): vnode = edge.get_other(unode) vid = vnode.id() if vid not in T: T.add(vid) l_vs[vid] = int(l_vs[u] + 1) P[nid][u] = vid Q.append(vid) # T = set([V[t] for t in T]) path_props = {"bfs-tree": P, "path-set": T, "top-sort": l_vs} return BaseGraphBFSResult( props=path_props, result_id="bfs-result-of-" + g.id(), search_name="breadth_first_search", data={}, )
def plus_minus_node_edge( g: AbstractGraph, el: Union[Set[AbstractNode], Set[AbstractEdge], AbstractGraph], is_plus=False, ) -> BaseGraph: """! \brief subtraction of elements for G - v cases, see Diestel p. 4 """ if isinstance(el, AbstractGraph): if is_plus is False: elnodes = set(el.V) nodes = {n for n in g.V if n not in elnodes} bg = BaseGraph.based_on_node_set(edges=set(g.E), nodes=nodes) bg.update_data(g.data()) return bg else: nodes = set(g.V).union(el.V) edges = set(g.E).union(el.E) bg = BaseGraph.from_edge_node_set(edges=edges, nodes=nodes) bg.update_data(g.data()) return bg nset = all(isinstance(e, AbstractNode) for e in el) if nset: if is_plus is False: nodes = {n for n in g.V if n not in el} edges = set(g.E) bg = BaseGraph.based_on_node_set(edges=edges, nodes=nodes) bg.update_data(g.data()) return bg else: nodes = set(g.V).union(el) edges = set(g.E) bg = BaseGraph.based_on_node_set(edges=edges, nodes=nodes) bg.update_data(g.data()) return bg eset = all(isinstance(e, AbstractEdge) for e in el) if eset: if is_plus is False: edges = {e for e in g.E if e not in el} bg = BaseGraph.from_edge_node_set(edges=edges, nodes=set(g.V)) bg.update_data(g.data()) return bg else: edges = set(g.E).union(el) bg = BaseGraph.from_edge_node_set(edges=edges, nodes=set(g.V)) bg.update_data(g.data()) return bg
def is_node_independent_of(g: AbstractGraph, n1: AbstractNode, n2: AbstractNode) -> bool: """! \brief check if two nodes are independent We consider two nodes independent if they are not the same, and they are not neighbours. \code{.py} >>> n1 = Node("n1", {}) >>> n2 = Node("n2", {}) >>> n3 = Node("n3", {}) >>> n4 = Node("n4", {}) >>> e1 = Edge( >>> "e1", start_node=n1, end_node=n2, edge_type=EdgeType.UNDIRECTED >>> ) >>> e2 = Edge( >>> "e2", start_node=n2, end_node=n3, edge_type=EdgeType.UNDIRECTED >>> ) >>> e3 = Edge( >>> "e3", start_node=n3, end_node=n4, edge_type=EdgeType.UNDIRECTED >>> ) >>> graph_2 = Graph( >>> "g2", >>> data={"my": "graph", "data": "is", "very": "awesome"}, >>> nodes=set([n1, n2, n3, n4]), >>> edges=set([e1, e2, e3]), >>> ) >>> graph_2.is_node_independent_of(n1, n3) >>> True \endcode """ if n1 == n2: return False return True if g.is_neighbour_of(n1, n2) is False else False
def is_disjoint(g1: AbstractGraph, g2: AbstractGraph) -> bool: "check if g2 is disjoint to g1" ns = BaseGraphOps.nodes(g1) ns_ = g2.vertex_intersection(ns) return len(ns_) == 0
def depth_first_search( g: AbstractGraph, edge_generator: Callable[[AbstractNode], Set[AbstractNode]], check_cycle: bool = False, start_node: Optional[AbstractNode] = None, ) -> BaseGraphDFSResult: """! \brief interior visit function for depth first enumeration of graph instance. \see dfs_forest() method for more information on parameters. """ V: Dict[str, AbstractNode] = {n.id(): n for n in g.V} if start_node is not None: if not BaseGraphOps.is_in(g, start_node): raise ValueError("Specified start node not in graph") # Vlst: List[str] = list(v for v in V.keys() if v != start_node.id()) Vlst.insert(0, start_node.id()) Vlst.sort() else: Vlst = list(v for v in V.keys()) Vlst.sort() time = 0 marked: Dict[str, bool] = {n: False for n in V} preds: Dict[str, Dict[str, str]] = {} Ts: Dict[str, Set[str]] = {} d: Dict[str, int] = {n: math.inf for n in V} f: Dict[str, int] = {n: math.inf for n in V} cycles: Dict[str, List[Dict[str, Union[str, int]]]] = { n: [] for n in V } component_counter = 0 # for u in Vlst: if marked[u] is False: pred: Dict[str, Optional[str]] = {n: None for n in V} T: Set[str] = set() BaseGraphSearcher.dfs_forest( g=g, V=V, u=u, pred=pred, cycles=cycles, marked=marked, d=d, T=T, f=f, time=time, check_cycle=check_cycle, edge_generator=edge_generator, ) component_counter += 1 for child, parent in pred.copy().items(): if child != u and child is None: pred.pop(child) Ts[u] = T preds[u] = pred # res = { "dfs-forest": BaseGraphSearcher.from_preds_to_edgeset(g, preds), "dfs-trees": preds, "first-visit-times": d, "last-visit-times": f, "components": Ts, "cycle-info": cycles, "nb-component": component_counter, } return BaseGraphDFSResult( props=res, result_id="dfs-result-of-" + g.id(), search_name="depth_first_search", data={}, )