예제 #1
0
    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)
예제 #2
0
 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
예제 #3
0
 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
예제 #4
0
 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)
예제 #5
0
 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]))
예제 #6
0
    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),
        )
예제 #7
0
 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
예제 #8
0
 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
예제 #9
0
    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
예제 #10
0
    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
예제 #11
0
    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
예제 #12
0
    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)
예제 #13
0
    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
예제 #14
0
 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
예제 #15
0
    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={},
        )
예제 #16
0
    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
예제 #17
0
 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
예제 #18
0
    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
예제 #19
0
 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,
         },
     )
예제 #20
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,
         },
     )
예제 #21
0
    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))
예제 #22
0
 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]
예제 #23
0
 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)
예제 #24
0
 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)
예제 #25
0
    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
예제 #26
0
 def egen(x):
     return BaseGraphOps.edges_of(self, x)
예제 #27
0
 def egen(x):
     return BaseGraphOps.outgoing_edges_of(self, x)
예제 #28
0
    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,
        )
예제 #29
0
 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
예제 #30
0
 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])
     )