def average_degree(g: AbstractGraph) -> float: """! \brief obtain the average degree of graph instance The average degree is calculated using the formula: \f[ d(G) = \frac{1}{V[G]} \sum_{v \in V[G]} d(v) \f] It can be found in Diestel 2017, p. 5 """ gdata = BaseGraphOps.to_edgelist(g) return sum([len(gdata[v.id()]) for v in g.V]) / len(g.V)
def find_maximal_cliques(self): """! find maximal cliques in graph using Bron Kerbosch algorithm as per arxiv.org/1006.5440 """ P: Set[Node] = BaseGraphOps.nodes(self) X: Set[Node] = set() R: Set[Node] = set() Cs: List[Set[Node]] = [] self.bron_kerbosch(P, R, X, Cs) return Cs
def graph_props(self): """! Several graph properties computed with dfs passage """ if self._props is None: self._props = BaseGraphSearcher.depth_first_search( self, edge_generator=lambda node: BaseGraphOps.edges_of(self, node), check_cycle=True, ) return self._props
def cond_prod_by_variable_elimination( self, queries: Set[NumCatRVariable], evidences: Set[Tuple[str, NumericValue]], ordering_fn=min_unmarked_neighbours, ): """! Compute conditional probabilities with variable elimination from Koller and Friedman 2009, p. 304 """ if queries.issubset(BaseGraphOps.nodes(self)) is False: raise ValueError( "Query variables must be a subset of vertices of graph") queries = self.reduce_queries_with_evidence(queries, evidences) factors, E = self.reduce_factors_with_evidence(evidences) Zs = set() for z in BaseGraphOps.nodes(self): if z not in E and z not in queries: Zs.add(z) return self.conditional_prod_by_variable_elimination( queries=queries, Zs=Zs, factors=factors, ordering_fn=ordering_fn)
def test_edges_by_end(self): """""" n1 = Node("n1", {}) n2 = Node("n2", {}) e1 = Edge( "e1", start_node=n1, end_node=n2, edge_type=EdgeType.UNDIRECTED ) e2 = Edge( "e2", start_node=n1, end_node=n1, edge_type=EdgeType.UNDIRECTED ) g = BaseGraph("g", nodes=set([n1, n2]), edges=set([e1, e2])) self.assertEqual(BaseGraphOps.edges_by_end(g, n2), set([e1]))
def find_shortest_paths(self, n1: Node) -> Dict[str, Union[dict, set]]: """! \brief Find shortest path between given node and all other nodes. This mostly the same function from Graph with the difference being the edge generating function. We consider every edge that is incident with nodes not just incoming or outgoing edges. """ return BaseGraphSearcher.breadth_first_search( self, n1=n1, edge_generator=lambda x: BaseGraphOps.edges_of(self, x), )
def order_by_greedy_metric( self, nodes: Set[NumCatRVariable], s: Callable[[Graph, Dict[Node, bool]], Optional[Node]] = min_unmarked_neighbours, ) -> Dict[str, int]: """! From Koller and Friedman 2009, p. 314 """ marked = {n.id(): False for n in nodes} cardinality = {n.id(): -1 for n in nodes} for i in range(len(nodes)): X = s(g=self, nodes=nodes, marked=marked) if X is not None: cardinality[X.id()] = i TEMP = BaseGraphOps.neighbours_of(self, X) while TEMP: n_x = TEMP.pop() for n in BaseGraphOps.neighbours_of(self, X): self = BaseGraphAlgOps.added_edge_between_if_none( self, n_x, n, is_directed=False) marked[X.id()] = True return cardinality
def extract_path( self, start: Node, end: Node, filter_fn: Callable[[Set[Edge], str], Set[Edge]] = lambda es, n: set( [e for e in es if e.start().id() == n]), costfn: Callable[[Edge, float], float] = lambda x, y: y + 1.0, is_min=True, ): """""" if (BaseGraphOps.is_in(self, start) is False or BaseGraphOps.is_in(self, end) is False): raise ValueError("start or end node is not inside tree") # upset = self.upset_of(start) if end not in upset: raise ValueError("end node is not in upset of start.") downset = self.downset_of(end) upset_edges = set() for u in upset: for e in BaseGraphOps.outgoing_edges_of(self, u): upset_edges.add(e) downset_edges = set() for d in downset: for e in BaseGraphOps.outgoing_edges_of(self, d): downset_edges.add(e) problem_set = upset_edges.intersection(downset_edges) ucs_path = Path.from_ucs( g=self, goal=end, start=start, filter_fn=filter_fn, costfn=costfn, is_min=is_min, problem_set=problem_set, ) return ucs_path
def comp_degree(g: AbstractGraph, fn: Callable[[int, int], bool], comp_val: int) -> int: """! \brief generic comparison function for degree related operations It is used in the context of finding maximum or minimum degree of the graph instance. """ compare_v = comp_val gdata = BaseGraphOps.to_edgelist(g) for nid in g.V: nb_edges = len(gdata[nid.id()]) if fn(nb_edges, compare_v): compare_v = nb_edges return compare_v
def find_maximum_spanning_tree(self, weight_fn=lambda x: 1): """! \brief obtain maximum weight spanning tree from graph. \see find_minimum_spanning_tree() \param weight_fn weighting function for edges. """ # t = Tree.find_mst_prim(self, edge_generator=self.edges_of) t, L = Tree.find_mnmx_st( self, edge_generator=lambda x: BaseGraphOps.edges_of(self, x), weight_function=weight_fn, is_min=False, ) return t, L
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 get_subgraph_by_vertices( self, vs: Set[Node], edge_policy: Callable[[Edge, Set[Node]], bool] = lambda x, ys: set( [x.start(), x.end()]).issubset(ys) is True, ) -> GraphObject: """! Get the subgraph using vertices. \param vs set of vertices for the subgraph \param edge_policy determines which edges should be conserved. By default we conserve edges whose incident nodes are a subset of vs """ es: Set[Edge] = set() for e in BaseGraphOps.edges(self): if edge_policy(e, vs) is True: es.add(e) return Graph.from_edge_node_set(edges=es, nodes=vs)
def find_articulation_points( self, graph_maker: Callable[[Node], GraphObject]) -> Set[Node]: """! \brief find articulation points of graph. Find the articulation points of a graph. An articulation point, also called cut vertex is defined as the vertex that separates two other vertices of the same component. The algorithm we implement here is the naive version see, Erciyes 2018, p. 228. For the definition of the cut vertex, see Diestel 2017, p. 11 """ nb_component = self.nb_components() points: Set[Node] = set() for node in BaseGraphOps.nodes(self): graph = graph_maker(node) if graph.nb_components() > nb_component: points.add(node) return points
def max_product_ve(self, evidences: Set[Tuple[str, NumericValue]]): """! Compute most probable assignments given evidences """ factors, E = self.reduce_factors_with_evidence(evidences) Zs = set() for z in BaseGraphOps.nodes(self): if z not in E: Zs.add(z) cardinality = self.order_by_greedy_metric(nodes=Zs, s=min_unmarked_neighbours) V = {v.id(): v for v in self.V} ordering = [ V[n[0]] for n in sorted(list(cardinality.items()), key=lambda x: x[1]) ] assignments, factors, z_phi = self.max_product_eliminate_vars( factors=factors, Zs=ordering) return assignments, factors, z_phi
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 find_minimum_spanning_tree(self, weight_fn: Callable[[Edge], int] = lambda x: 1): """! \brief Obtain the minimum spanning tree of the graph instance \param weight_fn weighting function used to extract weights from edges. We apply the generic weighted tree extraction algorithm from Tree. We consider that the graph is evenly weighted, however if this is not the case the weighting function can be used for determining weight of each edge. """ # t = Tree.find_mst_prim(self, edge_generator=self.edges_of) t, L = Tree.find_mnmx_st( self, edge_generator=lambda x: BaseGraphOps.edges_of(self, x), weight_function=weight_fn, ) return t, L
def from_preds_to_edgeset( g: AbstractGraph, preds: Dict[str, Dict[str, str]] ) -> Dict[str, Set[AbstractEdge]]: """! \brief obtain the edge set implied by the predecessor array. """ esets: Dict[str, Set[AbstractEdge]] = {} V = {v.id(): v for v in g.V} for u, forest in preds.copy().items(): eset: Set[AbstractEdge] = set() for child, parent in forest.items(): cnode = V[child] if parent is not None: pnode = V[parent] eset = eset.union( BaseGraphOps.edge_by_vertices( g, start=pnode, end=cnode ) ) esets[u] = eset return esets
def has_self_loop(g: AbstractGraph) -> bool: """! \brief Check if graph has a self loop. We check whether the incident vertices of an edge is same. \code{.py} n1 = Node("n1", {}) n2 = Node("n2", {}) e1 = Edge( "e1", start_node=n1, end_node=n2, edge_type=EdgeType.UNDIRECTED ) e2 = Edge( "e1", start_node=n1, end_node=n1, edge_type=EdgeType.UNDIRECTED ) g = Graph("graph", nodes=set([n1, n2]), edges=set([e1, e2])) g.has_self_loop() # True \endcode """ for edge in BaseGraphOps.edges(g): if edge.start() == edge.end(): return True return False
def test_adjmat_int(self): """""" mat = BaseGraphOps.to_adjmat(self.ugraph1) self.assertEqual( mat, { ("b", "b"): 0, ("b", "e"): 0, ("b", "f"): 0, ("b", "a"): 0, ("e", "b"): 0, ("e", "e"): 0, ("e", "f"): 1, ("e", "a"): 1, ("f", "b"): 0, ("f", "e"): 1, ("f", "f"): 0, ("f", "a"): 1, ("a", "b"): 0, ("a", "e"): 1, ("a", "f"): 1, ("a", "a"): 0, }, )
def test_adjmat_bool(self): """""" mat = BaseGraphOps.to_adjmat(self.ugraph1, vtype=bool) self.assertEqual( mat, { ("b", "b"): False, ("b", "e"): False, ("b", "f"): False, ("b", "a"): False, ("e", "b"): False, ("e", "e"): False, ("e", "f"): True, ("e", "a"): True, ("f", "b"): False, ("f", "e"): True, ("f", "f"): False, ("f", "a"): True, ("a", "b"): False, ("a", "e"): True, ("a", "f"): True, ("a", "a"): False, }, )
def nb_neighbours_of(g: AbstractGraph, n: AbstractNode) -> int: """! \brief obtain number of neighbours of a given node. \param n node whose neighbour set we are interested in. \see Graph.neighbours_of Number of nodes in the neighbour set of n. \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.nb_neighbours_of(n2) >>> 2 \endcode """ return len(BaseGraphOps.neighbours_of(g, n))
def height_of(self, n: Node) -> int: """!""" if not BaseGraphOps.is_in(self, n): raise ValueError("node not in tree") nid = n.id() return self.topsort[nid]
def test_incoming_edges_of_1(self): """""" out_edges1 = BaseGraphOps.incoming_edges_of(self.graph_2, self.n1) comp1 = frozenset() self.assertEqual(out_edges1, comp1)
def test_incoming_edges_of_2(self): """""" out_edges2 = BaseGraphOps.incoming_edges_of(self.graph_2, self.n2) comp2 = frozenset([self.e1]) self.assertEqual(out_edges2, comp2)
def to_adjmat(self, vtype=int) -> Dict[Tuple[str, str], int]: """! \brief Transform adjacency list to adjacency matrix representation \param vtype the cast type for the entry of adjacency matrix. \return adjacency matrix whose keys are identifiers of nodes and values are flags whether there is an edge between them. \code{.py} >>> a = Node("a", {}) # b >>> b = Node("b", {}) # c >>> f = Node("f", {}) # d >>> e = Node("e", {}) # e >>> ae = Edge( >>> "ae", start_node=a, end_node=e, edge_type=EdgeType.UNDIRECTED >>> ) >>> af = Edge( >>> "af", start_node=a, end_node=f, edge_type=EdgeType.UNDIRECTED >>> ) >>> ef = Edge( >>> "ef", start_node=e, end_node=f, edge_type=EdgeType.UNDIRECTED >>> ) >>> ugraph1 = Graph( >>> "graph", >>> data={"my": "graph", "data": "is", "very": "awesome"}, >>> nodes=set([a, b, e, f]), >>> edges=set([ae, af, ef]), >>> ) >>> mat = ugraph1.to_adjmat(vtype=bool) >>> mat == { >>> ("b", "b"): False, >>> ("b", "e"): False, >>> ("b", "f"): False, >>> ("b", "a"): False, >>> ("e", "b"): False, >>> ("e", "e"): False, >>> ("e", "f"): True, >>> ("e", "a"): True, >>> ("f", "b"): False, >>> ("f", "e"): True, >>> ("f", "f"): False, >>> ("f", "a"): True, >>> ("a", "b"): False, >>> ("a", "e"): True, >>> ("a", "f"): True, >>> ("a", "a"): False >>> } >>> True \endcode """ gmat = {} for v in self.V: for k in self.V: gmat[(v.id(), k.id())] = vtype(0) for edge in BaseGraphOps.edges(self): tpl1 = (edge.start().id(), edge.end().id()) tpl2 = (edge.end().id(), edge.start().id()) if tpl1 in gmat: gmat[tpl1] = vtype(1) if edge.type() == EdgeType.UNDIRECTED: if tpl2 in gmat: gmat[tpl2] = vtype(1) return gmat
def egen(x): return BaseGraphOps.edges_of(self, x)
def egen(x): return BaseGraphOps.outgoing_edges_of(self, x)
def setUp(self): self.n1 = Node("n1", {}) self.n2 = Node("n2", {}) self.n3 = Node("n3", {}) self.n4 = Node("n4", {}) self.n5 = Node("n5", {}) self.e1 = Edge( "e1", start_node=self.n1, end_node=self.n2, edge_type=EdgeType.UNDIRECTED, ) self.e2 = Edge( "e2", start_node=self.n2, end_node=self.n3, edge_type=EdgeType.UNDIRECTED, ) self.e3 = Edge( "e3", start_node=self.n3, end_node=self.n4, edge_type=EdgeType.UNDIRECTED, ) self.e4 = Edge( "e4", start_node=self.n1, end_node=self.n4, edge_type=EdgeType.UNDIRECTED, ) self.graph = BaseGraph( "g1", data={"my": "graph", "data": "is", "very": "awesome"}, nodes=set([self.n1, self.n2, self.n3, self.n4]), edges=set([self.e1, self.e2]), ) self.graph_2 = BaseGraph( "g2", data={"my": "graph", "data": "is", "very": "awesome"}, nodes=set([self.n1, self.n2, self.n3, self.n4]), edges=set([self.e1, self.e2, self.e3]), ) # self.a = Node("a", {}) # b self.b = Node("b", {}) # c self.f = Node("f", {}) # d self.e = Node("e", {}) # e self.ae = Edge( "ae", start_node=self.a, end_node=self.e, edge_type=EdgeType.UNDIRECTED, ) self.ab = Edge( "ab", start_node=self.a, end_node=self.b, edge_type=EdgeType.UNDIRECTED, ) self.af = Edge( "af", start_node=self.a, end_node=self.f, edge_type=EdgeType.UNDIRECTED, ) self.be = Edge( "be", start_node=self.b, end_node=self.e, edge_type=EdgeType.UNDIRECTED, ) self.ef = Edge( "ef", start_node=self.e, end_node=self.f, edge_type=EdgeType.UNDIRECTED, ) # undirected graph self.ugraph2 = BaseGraph( "ug2", data={"my": "graph", "data": "is", "very": "awesome"}, nodes=set([self.a, self.b, self.e, self.f]), edges=set( [ self.ae, self.ab, self.af, self.be, self.ef, ] ), ) # ugraph2 : # +-----+ # / \ # a -- b -- e # \ / # +-----f self.ugraph3 = BaseGraph( "ug3", data={"my": "graph", "data": "is", "very": "awesome"}, nodes=set([self.a, self.b, self.e, self.f]), edges=set( [ self.ab, # self.af, self.be, ] ), ) # ugraph3 : # # # a -- b -- e # \ # +-----f self.ugraph4 = BaseGraph( "ug4", data={"my": "graph", "data": "is", "very": "awesome"}, nodes=set(BaseGraphOps.nodes(self.ugraph2)).union( BaseGraphOps.nodes(self.graph_2) ), edges=BaseGraphOps.edges(self.ugraph2).union( BaseGraphOps.edges(self.graph_2) ), ) # ugraph 4 # +-----+ n1 -- n2 -- n3 -- n4 # / \ \ / # a -- b -- e +--------------+ # \ / # +-----f # make some directed edges self.bb = Node("bb", {}) self.cc = Node("cc", {}) self.dd = Node("dd", {}) self.ee = Node("ee", {}) self.bb_cc = Edge( "bb_cc", start_node=self.bb, end_node=self.cc, edge_type=EdgeType.DIRECTED, ) self.cc_dd = Edge( "cc_dd", start_node=self.cc, end_node=self.dd, edge_type=EdgeType.DIRECTED, ) self.dd_ee = Edge( "dd_ee", start_node=self.dd, end_node=self.ee, edge_type=EdgeType.DIRECTED, ) self.ee_bb = Edge( "ee_bb", start_node=self.ee, end_node=self.bb, edge_type=EdgeType.DIRECTED, ) self.bb_dd = Edge( "bb_dd", start_node=self.bb, end_node=self.dd, edge_type=EdgeType.DIRECTED, )
def is_set_of(self, n: Node, fn: Callable[[Node, Node], bool]) -> Set[Node]: nodes = BaseGraphOps.nodes(self) nset = set([y for y in nodes if fn(n, y) is True]) return nset
def test_add_node(self): n = Node("n646", {}) g = BaseGraphAlgOps.add(self.graph, n) self.assertEqual( BaseGraphOps.nodes(g), set([self.n1, self.n2, self.n3, self.n4, n]) )