aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/networkx/classes/tests
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/networkx/classes/tests
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are hereHEADmaster
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/classes/tests')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/dispatch_interface.py185
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/historical_tests.py475
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/test_coreviews.py362
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/test_digraph.py331
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/test_digraph_historical.py111
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/test_filters.py177
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/test_function.py1035
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/test_graph.py920
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/test_graph_historical.py13
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/test_graphviews.py350
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/test_multidigraph.py459
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/test_multigraph.py528
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/test_reportviews.py1435
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/test_special.py131
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/classes/tests/test_subgraphviews.py362
16 files changed, 6874 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/__init__.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/dispatch_interface.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/dispatch_interface.py
new file mode 100644
index 00000000..5cc908d7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/dispatch_interface.py
@@ -0,0 +1,185 @@
+# This file contains utilities for testing the dispatching feature
+
+# A full test of all dispatchable algorithms is performed by
+# modifying the pytest invocation and setting an environment variable
+# NETWORKX_TEST_BACKEND=nx_loopback pytest
+# This is comprehensive, but only tests the `test_override_dispatch`
+# function in networkx.classes.backends.
+
+# To test the `_dispatchable` function directly, several tests scattered throughout
+# NetworkX have been augmented to test normal and dispatch mode.
+# Searching for `dispatch_interface` should locate the specific tests.
+
+import networkx as nx
+from networkx import DiGraph, Graph, MultiDiGraph, MultiGraph, PlanarEmbedding
+from networkx.classes.reportviews import NodeView
+
+
+class LoopbackGraph(Graph):
+ __networkx_backend__ = "nx_loopback"
+
+
+class LoopbackDiGraph(DiGraph):
+ __networkx_backend__ = "nx_loopback"
+
+
+class LoopbackMultiGraph(MultiGraph):
+ __networkx_backend__ = "nx_loopback"
+
+
+class LoopbackMultiDiGraph(MultiDiGraph):
+ __networkx_backend__ = "nx_loopback"
+
+
+class LoopbackPlanarEmbedding(PlanarEmbedding):
+ __networkx_backend__ = "nx_loopback"
+
+
+def convert(graph):
+ if isinstance(graph, PlanarEmbedding):
+ return LoopbackPlanarEmbedding(graph)
+ if isinstance(graph, MultiDiGraph):
+ return LoopbackMultiDiGraph(graph)
+ if isinstance(graph, MultiGraph):
+ return LoopbackMultiGraph(graph)
+ if isinstance(graph, DiGraph):
+ return LoopbackDiGraph(graph)
+ if isinstance(graph, Graph):
+ return LoopbackGraph(graph)
+ raise TypeError(f"Unsupported type of graph: {type(graph)}")
+
+
+class LoopbackBackendInterface:
+ def __getattr__(self, item):
+ try:
+ return nx.utils.backends._registered_algorithms[item].orig_func
+ except KeyError:
+ raise AttributeError(item) from None
+
+ @staticmethod
+ def convert_from_nx(
+ graph,
+ *,
+ edge_attrs=None,
+ node_attrs=None,
+ preserve_edge_attrs=None,
+ preserve_node_attrs=None,
+ preserve_graph_attrs=None,
+ name=None,
+ graph_name=None,
+ ):
+ if name in {
+ # Raise if input graph changes. See test_dag.py::test_topological_sort6
+ "lexicographical_topological_sort",
+ "topological_generations",
+ "topological_sort",
+ # Would be nice to some day avoid these cutoffs of full testing
+ }:
+ return graph
+ if isinstance(graph, NodeView):
+ # Convert to a Graph with only nodes (no edges)
+ new_graph = Graph()
+ new_graph.add_nodes_from(graph.items())
+ graph = new_graph
+ G = LoopbackGraph()
+ elif not isinstance(graph, Graph):
+ raise TypeError(
+ f"Bad type for graph argument {graph_name} in {name}: {type(graph)}"
+ )
+ elif graph.__class__ in {Graph, LoopbackGraph}:
+ G = LoopbackGraph()
+ elif graph.__class__ in {DiGraph, LoopbackDiGraph}:
+ G = LoopbackDiGraph()
+ elif graph.__class__ in {MultiGraph, LoopbackMultiGraph}:
+ G = LoopbackMultiGraph()
+ elif graph.__class__ in {MultiDiGraph, LoopbackMultiDiGraph}:
+ G = LoopbackMultiDiGraph()
+ elif graph.__class__ in {PlanarEmbedding, LoopbackPlanarEmbedding}:
+ G = LoopbackDiGraph() # or LoopbackPlanarEmbedding
+ else:
+ # Would be nice to handle these better some day
+ # nx.algorithms.approximation.kcomponents._AntiGraph
+ # nx.classes.tests.test_multidigraph.MultiDiGraphSubClass
+ # nx.classes.tests.test_multigraph.MultiGraphSubClass
+ G = graph.__class__()
+
+ if preserve_graph_attrs:
+ G.graph.update(graph.graph)
+
+ # add nodes
+ G.add_nodes_from(graph)
+ if preserve_node_attrs:
+ for n, dd in G._node.items():
+ dd.update(graph.nodes[n])
+ elif node_attrs:
+ for n, dd in G._node.items():
+ dd.update(
+ (attr, graph._node[n].get(attr, default))
+ for attr, default in node_attrs.items()
+ if default is not None or attr in graph._node[n]
+ )
+
+ # tools to build datadict and keydict
+ if preserve_edge_attrs:
+
+ def G_new_datadict(old_dd):
+ return G.edge_attr_dict_factory(old_dd)
+ elif edge_attrs:
+
+ def G_new_datadict(old_dd):
+ return G.edge_attr_dict_factory(
+ (attr, old_dd.get(attr, default))
+ for attr, default in edge_attrs.items()
+ if default is not None or attr in old_dd
+ )
+ else:
+
+ def G_new_datadict(old_dd):
+ return G.edge_attr_dict_factory()
+
+ if G.is_multigraph():
+
+ def G_new_inner(keydict):
+ kd = G.adjlist_inner_dict_factory(
+ (k, G_new_datadict(dd)) for k, dd in keydict.items()
+ )
+ return kd
+ else:
+ G_new_inner = G_new_datadict
+
+ # add edges keeping the same order in _adj and _pred
+ G_adj = G._adj
+ if G.is_directed():
+ for n, nbrs in graph._adj.items():
+ G_adj[n].update((nbr, G_new_inner(dd)) for nbr, dd in nbrs.items())
+ # ensure same datadict for pred and adj; and pred order of graph._pred
+ G_pred = G._pred
+ for n, nbrs in graph._pred.items():
+ G_pred[n].update((nbr, G_adj[nbr][n]) for nbr in nbrs)
+ else: # undirected
+ for n, nbrs in graph._adj.items():
+ # ensure same datadict for both ways; and adj order of graph._adj
+ G_adj[n].update(
+ (nbr, G_adj[nbr][n] if n in G_adj[nbr] else G_new_inner(dd))
+ for nbr, dd in nbrs.items()
+ )
+
+ return G
+
+ @staticmethod
+ def convert_to_nx(obj, *, name=None):
+ return obj
+
+ @staticmethod
+ def on_start_tests(items):
+ # Verify that items can be xfailed
+ for item in items:
+ assert hasattr(item, "add_marker")
+
+ def can_run(self, name, args, kwargs):
+ # It is unnecessary to define this function if algorithms are fully supported.
+ # We include it for illustration purposes.
+ return hasattr(self, name)
+
+
+backend_interface = LoopbackBackendInterface()
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/historical_tests.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/historical_tests.py
new file mode 100644
index 00000000..9dad24e2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/historical_tests.py
@@ -0,0 +1,475 @@
+"""Original NetworkX graph tests"""
+
+import pytest
+
+import networkx as nx
+from networkx import convert_node_labels_to_integers as cnlti
+from networkx.utils import edges_equal, nodes_equal
+
+
+class HistoricalTests:
+ @classmethod
+ def setup_class(cls):
+ cls.null = nx.null_graph()
+ cls.P1 = cnlti(nx.path_graph(1), first_label=1)
+ cls.P3 = cnlti(nx.path_graph(3), first_label=1)
+ cls.P10 = cnlti(nx.path_graph(10), first_label=1)
+ cls.K1 = cnlti(nx.complete_graph(1), first_label=1)
+ cls.K3 = cnlti(nx.complete_graph(3), first_label=1)
+ cls.K4 = cnlti(nx.complete_graph(4), first_label=1)
+ cls.K5 = cnlti(nx.complete_graph(5), first_label=1)
+ cls.K10 = cnlti(nx.complete_graph(10), first_label=1)
+ cls.G = nx.Graph
+
+ def test_name(self):
+ G = self.G(name="test")
+ assert G.name == "test"
+ H = self.G()
+ assert H.name == ""
+
+ # Nodes
+
+ def test_add_remove_node(self):
+ G = self.G()
+ G.add_node("A")
+ assert G.has_node("A")
+ G.remove_node("A")
+ assert not G.has_node("A")
+
+ def test_nonhashable_node(self):
+ # Test if a non-hashable object is in the Graph. A python dict will
+ # raise a TypeError, but for a Graph class a simple False should be
+ # returned (see Graph __contains__). If it cannot be a node then it is
+ # not a node.
+ G = self.G()
+ assert not G.has_node(["A"])
+ assert not G.has_node({"A": 1})
+
+ def test_add_nodes_from(self):
+ G = self.G()
+ G.add_nodes_from(list("ABCDEFGHIJKL"))
+ assert G.has_node("L")
+ G.remove_nodes_from(["H", "I", "J", "K", "L"])
+ G.add_nodes_from([1, 2, 3, 4])
+ assert sorted(G.nodes(), key=str) == [
+ 1,
+ 2,
+ 3,
+ 4,
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G",
+ ]
+ # test __iter__
+ assert sorted(G, key=str) == [1, 2, 3, 4, "A", "B", "C", "D", "E", "F", "G"]
+
+ def test_contains(self):
+ G = self.G()
+ G.add_node("A")
+ assert "A" in G
+ assert [] not in G # never raise a Key or TypeError in this test
+ assert {1: 1} not in G
+
+ def test_add_remove(self):
+ # Test add_node and remove_node acting for various nbunch
+ G = self.G()
+ G.add_node("m")
+ assert G.has_node("m")
+ G.add_node("m") # no complaints
+ pytest.raises(nx.NetworkXError, G.remove_node, "j")
+ G.remove_node("m")
+ assert list(G) == []
+
+ def test_nbunch_is_list(self):
+ G = self.G()
+ G.add_nodes_from(list("ABCD"))
+ G.add_nodes_from(self.P3) # add nbunch of nodes (nbunch=Graph)
+ assert sorted(G.nodes(), key=str) == [1, 2, 3, "A", "B", "C", "D"]
+ G.remove_nodes_from(self.P3) # remove nbunch of nodes (nbunch=Graph)
+ assert sorted(G.nodes(), key=str) == ["A", "B", "C", "D"]
+
+ def test_nbunch_is_set(self):
+ G = self.G()
+ nbunch = set("ABCDEFGHIJKL")
+ G.add_nodes_from(nbunch)
+ assert G.has_node("L")
+
+ def test_nbunch_dict(self):
+ # nbunch is a dict with nodes as keys
+ G = self.G()
+ nbunch = set("ABCDEFGHIJKL")
+ G.add_nodes_from(nbunch)
+ nbunch = {"I": "foo", "J": 2, "K": True, "L": "spam"}
+ G.remove_nodes_from(nbunch)
+ assert sorted(G.nodes(), key=str), ["A", "B", "C", "D", "E", "F", "G", "H"]
+
+ def test_nbunch_iterator(self):
+ G = self.G()
+ G.add_nodes_from(["A", "B", "C", "D", "E", "F", "G", "H"])
+ n_iter = self.P3.nodes()
+ G.add_nodes_from(n_iter)
+ assert sorted(G.nodes(), key=str) == [
+ 1,
+ 2,
+ 3,
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G",
+ "H",
+ ]
+ n_iter = self.P3.nodes() # rebuild same iterator
+ G.remove_nodes_from(n_iter) # remove nbunch of nodes (nbunch=iterator)
+ assert sorted(G.nodes(), key=str) == ["A", "B", "C", "D", "E", "F", "G", "H"]
+
+ def test_nbunch_graph(self):
+ G = self.G()
+ G.add_nodes_from(["A", "B", "C", "D", "E", "F", "G", "H"])
+ nbunch = self.K3
+ G.add_nodes_from(nbunch)
+ assert sorted(G.nodes(), key=str), [
+ 1,
+ 2,
+ 3,
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G",
+ "H",
+ ]
+
+ # Edges
+
+ def test_add_edge(self):
+ G = self.G()
+ pytest.raises(TypeError, G.add_edge, "A")
+
+ G.add_edge("A", "B") # testing add_edge()
+ G.add_edge("A", "B") # should fail silently
+ assert G.has_edge("A", "B")
+ assert not G.has_edge("A", "C")
+ assert G.has_edge(*("A", "B"))
+ if G.is_directed():
+ assert not G.has_edge("B", "A")
+ else:
+ # G is undirected, so B->A is an edge
+ assert G.has_edge("B", "A")
+
+ G.add_edge("A", "C") # test directedness
+ G.add_edge("C", "A")
+ G.remove_edge("C", "A")
+ if G.is_directed():
+ assert G.has_edge("A", "C")
+ else:
+ assert not G.has_edge("A", "C")
+ assert not G.has_edge("C", "A")
+
+ def test_self_loop(self):
+ G = self.G()
+ G.add_edge("A", "A") # test self loops
+ assert G.has_edge("A", "A")
+ G.remove_edge("A", "A")
+ G.add_edge("X", "X")
+ assert G.has_node("X")
+ G.remove_node("X")
+ G.add_edge("A", "Z") # should add the node silently
+ assert G.has_node("Z")
+
+ def test_add_edges_from(self):
+ G = self.G()
+ G.add_edges_from([("B", "C")]) # test add_edges_from()
+ assert G.has_edge("B", "C")
+ if G.is_directed():
+ assert not G.has_edge("C", "B")
+ else:
+ assert G.has_edge("C", "B") # undirected
+
+ G.add_edges_from([("D", "F"), ("B", "D")])
+ assert G.has_edge("D", "F")
+ assert G.has_edge("B", "D")
+
+ if G.is_directed():
+ assert not G.has_edge("D", "B")
+ else:
+ assert G.has_edge("D", "B") # undirected
+
+ def test_add_edges_from2(self):
+ G = self.G()
+ # after failing silently, should add 2nd edge
+ G.add_edges_from([tuple("IJ"), list("KK"), tuple("JK")])
+ assert G.has_edge(*("I", "J"))
+ assert G.has_edge(*("K", "K"))
+ assert G.has_edge(*("J", "K"))
+ if G.is_directed():
+ assert not G.has_edge(*("K", "J"))
+ else:
+ assert G.has_edge(*("K", "J"))
+
+ def test_add_edges_from3(self):
+ G = self.G()
+ G.add_edges_from(zip(list("ACD"), list("CDE")))
+ assert G.has_edge("D", "E")
+ assert not G.has_edge("E", "C")
+
+ def test_remove_edge(self):
+ G = self.G()
+ G.add_nodes_from([1, 2, 3, "A", "B", "C", "D", "E", "F", "G", "H"])
+
+ G.add_edges_from(zip(list("MNOP"), list("NOPM")))
+ assert G.has_edge("O", "P")
+ assert G.has_edge("P", "M")
+ G.remove_node("P") # tests remove_node()'s handling of edges.
+ assert not G.has_edge("P", "M")
+ pytest.raises(TypeError, G.remove_edge, "M")
+
+ G.add_edge("N", "M")
+ assert G.has_edge("M", "N")
+ G.remove_edge("M", "N")
+ assert not G.has_edge("M", "N")
+
+ # self loop fails silently
+ G.remove_edges_from([list("HI"), list("DF"), tuple("KK"), tuple("JK")])
+ assert not G.has_edge("H", "I")
+ assert not G.has_edge("J", "K")
+ G.remove_edges_from([list("IJ"), list("KK"), list("JK")])
+ assert not G.has_edge("I", "J")
+ G.remove_nodes_from(set("ZEFHIMNO"))
+ G.add_edge("J", "K")
+
+ def test_edges_nbunch(self):
+ # Test G.edges(nbunch) with various forms of nbunch
+ G = self.G()
+ G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")])
+ # node not in nbunch should be quietly ignored
+ pytest.raises(nx.NetworkXError, G.edges, 6)
+ assert list(G.edges("Z")) == [] # iterable non-node
+ # nbunch can be an empty list
+ assert list(G.edges([])) == []
+ if G.is_directed():
+ elist = [("A", "B"), ("A", "C"), ("B", "D")]
+ else:
+ elist = [("A", "B"), ("A", "C"), ("B", "C"), ("B", "D")]
+ # nbunch can be a list
+ assert edges_equal(list(G.edges(["A", "B"])), elist)
+ # nbunch can be a set
+ assert edges_equal(G.edges({"A", "B"}), elist)
+ # nbunch can be a graph
+ G1 = self.G()
+ G1.add_nodes_from("AB")
+ assert edges_equal(G.edges(G1), elist)
+ # nbunch can be a dict with nodes as keys
+ ndict = {"A": "thing1", "B": "thing2"}
+ assert edges_equal(G.edges(ndict), elist)
+ # nbunch can be a single node
+ assert edges_equal(list(G.edges("A")), [("A", "B"), ("A", "C")])
+ assert nodes_equal(sorted(G), ["A", "B", "C", "D"])
+
+ # nbunch can be nothing (whole graph)
+ assert edges_equal(
+ list(G.edges()),
+ [("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")],
+ )
+
+ def test_degree(self):
+ G = self.G()
+ G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")])
+ assert G.degree("A") == 2
+
+ # degree of single node in iterable container must return dict
+ assert list(G.degree(["A"])) == [("A", 2)]
+ assert sorted(d for n, d in G.degree(["A", "B"])) == [2, 3]
+ assert sorted(d for n, d in G.degree()) == [2, 2, 3, 3]
+
+ def test_degree2(self):
+ H = self.G()
+ H.add_edges_from([(1, 24), (1, 2)])
+ assert sorted(d for n, d in H.degree([1, 24])) == [1, 2]
+
+ def test_degree_graph(self):
+ P3 = nx.path_graph(3)
+ P5 = nx.path_graph(5)
+ # silently ignore nodes not in P3
+ assert dict(d for n, d in P3.degree(["A", "B"])) == {}
+ # nbunch can be a graph
+ assert sorted(d for n, d in P5.degree(P3)) == [1, 2, 2]
+ # nbunch can be a graph that's way too big
+ assert sorted(d for n, d in P3.degree(P5)) == [1, 1, 2]
+ assert list(P5.degree([])) == []
+ assert dict(P5.degree([])) == {}
+
+ def test_null(self):
+ null = nx.null_graph()
+ assert list(null.degree()) == []
+ assert dict(null.degree()) == {}
+
+ def test_order_size(self):
+ G = self.G()
+ G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")])
+ assert G.order() == 4
+ assert G.size() == 5
+ assert G.number_of_edges() == 5
+ assert G.number_of_edges("A", "B") == 1
+ assert G.number_of_edges("A", "D") == 0
+
+ def test_copy(self):
+ G = self.G()
+ H = G.copy() # copy
+ assert H.adj == G.adj
+ assert H.name == G.name
+ assert H is not G
+
+ def test_subgraph(self):
+ G = self.G()
+ G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")])
+ SG = G.subgraph(["A", "B", "D"])
+ assert nodes_equal(list(SG), ["A", "B", "D"])
+ assert edges_equal(list(SG.edges()), [("A", "B"), ("B", "D")])
+
+ def test_to_directed(self):
+ G = self.G()
+ if not G.is_directed():
+ G.add_edges_from(
+ [("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")]
+ )
+
+ DG = G.to_directed()
+ assert DG is not G # directed copy or copy
+
+ assert DG.is_directed()
+ assert DG.name == G.name
+ assert DG.adj == G.adj
+ assert sorted(DG.out_edges(list("AB"))) == [
+ ("A", "B"),
+ ("A", "C"),
+ ("B", "A"),
+ ("B", "C"),
+ ("B", "D"),
+ ]
+ DG.remove_edge("A", "B")
+ assert DG.has_edge("B", "A") # this removes B-A but not A-B
+ assert not DG.has_edge("A", "B")
+
+ def test_to_undirected(self):
+ G = self.G()
+ if G.is_directed():
+ G.add_edges_from(
+ [("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")]
+ )
+ UG = G.to_undirected() # to_undirected
+ assert UG is not G
+ assert not UG.is_directed()
+ assert G.is_directed()
+ assert UG.name == G.name
+ assert UG.adj != G.adj
+ assert sorted(UG.edges(list("AB"))) == [
+ ("A", "B"),
+ ("A", "C"),
+ ("B", "C"),
+ ("B", "D"),
+ ]
+ assert sorted(UG.edges(["A", "B"])) == [
+ ("A", "B"),
+ ("A", "C"),
+ ("B", "C"),
+ ("B", "D"),
+ ]
+ UG.remove_edge("A", "B")
+ assert not UG.has_edge("B", "A")
+ assert not UG.has_edge("A", "B")
+
+ def test_neighbors(self):
+ G = self.G()
+ G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")])
+ G.add_nodes_from("GJK")
+ assert sorted(G["A"]) == ["B", "C"]
+ assert sorted(G.neighbors("A")) == ["B", "C"]
+ assert sorted(G.neighbors("A")) == ["B", "C"]
+ assert sorted(G.neighbors("G")) == []
+ pytest.raises(nx.NetworkXError, G.neighbors, "j")
+
+ def test_iterators(self):
+ G = self.G()
+ G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")])
+ G.add_nodes_from("GJK")
+ assert sorted(G.nodes()) == ["A", "B", "C", "D", "G", "J", "K"]
+ assert edges_equal(
+ G.edges(), [("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")]
+ )
+
+ assert sorted(v for k, v in G.degree()) == [0, 0, 0, 2, 2, 3, 3]
+ assert sorted(G.degree(), key=str) == [
+ ("A", 2),
+ ("B", 3),
+ ("C", 3),
+ ("D", 2),
+ ("G", 0),
+ ("J", 0),
+ ("K", 0),
+ ]
+ assert sorted(G.neighbors("A")) == ["B", "C"]
+ pytest.raises(nx.NetworkXError, G.neighbors, "X")
+ G.clear()
+ assert nx.number_of_nodes(G) == 0
+ assert nx.number_of_edges(G) == 0
+
+ def test_null_subgraph(self):
+ # Subgraph of a null graph is a null graph
+ nullgraph = nx.null_graph()
+ G = nx.null_graph()
+ H = G.subgraph([])
+ assert nx.is_isomorphic(H, nullgraph)
+
+ def test_empty_subgraph(self):
+ # Subgraph of an empty graph is an empty graph. test 1
+ nullgraph = nx.null_graph()
+ E5 = nx.empty_graph(5)
+ E10 = nx.empty_graph(10)
+ H = E10.subgraph([])
+ assert nx.is_isomorphic(H, nullgraph)
+ H = E10.subgraph([1, 2, 3, 4, 5])
+ assert nx.is_isomorphic(H, E5)
+
+ def test_complete_subgraph(self):
+ # Subgraph of a complete graph is a complete graph
+ K1 = nx.complete_graph(1)
+ K3 = nx.complete_graph(3)
+ K5 = nx.complete_graph(5)
+ H = K5.subgraph([1, 2, 3])
+ assert nx.is_isomorphic(H, K3)
+
+ def test_subgraph_nbunch(self):
+ nullgraph = nx.null_graph()
+ K1 = nx.complete_graph(1)
+ K3 = nx.complete_graph(3)
+ K5 = nx.complete_graph(5)
+ # Test G.subgraph(nbunch), where nbunch is a single node
+ H = K5.subgraph(1)
+ assert nx.is_isomorphic(H, K1)
+ # Test G.subgraph(nbunch), where nbunch is a set
+ H = K5.subgraph({1})
+ assert nx.is_isomorphic(H, K1)
+ # Test G.subgraph(nbunch), where nbunch is an iterator
+ H = K5.subgraph(iter(K3))
+ assert nx.is_isomorphic(H, K3)
+ # Test G.subgraph(nbunch), where nbunch is another graph
+ H = K5.subgraph(K3)
+ assert nx.is_isomorphic(H, K3)
+ H = K5.subgraph([9])
+ assert nx.is_isomorphic(H, nullgraph)
+
+ def test_node_tuple_issue(self):
+ H = self.G()
+ # Test error handling of tuple as a node
+ pytest.raises(nx.NetworkXError, H.remove_node, (1, 2))
+ H.remove_nodes_from([(1, 2)]) # no error
+ pytest.raises(nx.NetworkXError, H.neighbors, (1, 2))
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_coreviews.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_coreviews.py
new file mode 100644
index 00000000..24de7f2f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_coreviews.py
@@ -0,0 +1,362 @@
+import pickle
+
+import pytest
+
+import networkx as nx
+
+
+class TestAtlasView:
+ # node->data
+ def setup_method(self):
+ self.d = {0: {"color": "blue", "weight": 1.2}, 1: {}, 2: {"color": 1}}
+ self.av = nx.classes.coreviews.AtlasView(self.d)
+
+ def test_pickle(self):
+ view = self.av
+ pview = pickle.loads(pickle.dumps(view, -1))
+ assert view == pview
+ assert view.__slots__ == pview.__slots__
+ pview = pickle.loads(pickle.dumps(view))
+ assert view == pview
+ assert view.__slots__ == pview.__slots__
+
+ def test_len(self):
+ assert len(self.av) == len(self.d)
+
+ def test_iter(self):
+ assert list(self.av) == list(self.d)
+
+ def test_getitem(self):
+ assert self.av[1] is self.d[1]
+ assert self.av[2]["color"] == 1
+ pytest.raises(KeyError, self.av.__getitem__, 3)
+
+ def test_copy(self):
+ avcopy = self.av.copy()
+ assert avcopy[0] == self.av[0]
+ assert avcopy == self.av
+ assert avcopy[0] is not self.av[0]
+ assert avcopy is not self.av
+ avcopy[5] = {}
+ assert avcopy != self.av
+
+ avcopy[0]["ht"] = 4
+ assert avcopy[0] != self.av[0]
+ self.av[0]["ht"] = 4
+ assert avcopy[0] == self.av[0]
+ del self.av[0]["ht"]
+
+ assert not hasattr(self.av, "__setitem__")
+
+ def test_items(self):
+ assert sorted(self.av.items()) == sorted(self.d.items())
+
+ def test_str(self):
+ out = str(self.d)
+ assert str(self.av) == out
+
+ def test_repr(self):
+ out = "AtlasView(" + str(self.d) + ")"
+ assert repr(self.av) == out
+
+
+class TestAdjacencyView:
+ # node->nbr->data
+ def setup_method(self):
+ dd = {"color": "blue", "weight": 1.2}
+ self.nd = {0: dd, 1: {}, 2: {"color": 1}}
+ self.adj = {3: self.nd, 0: {3: dd}, 1: {}, 2: {3: {"color": 1}}}
+ self.adjview = nx.classes.coreviews.AdjacencyView(self.adj)
+
+ def test_pickle(self):
+ view = self.adjview
+ pview = pickle.loads(pickle.dumps(view, -1))
+ assert view == pview
+ assert view.__slots__ == pview.__slots__
+
+ def test_len(self):
+ assert len(self.adjview) == len(self.adj)
+
+ def test_iter(self):
+ assert list(self.adjview) == list(self.adj)
+
+ def test_getitem(self):
+ assert self.adjview[1] is not self.adj[1]
+ assert self.adjview[3][0] is self.adjview[0][3]
+ assert self.adjview[2][3]["color"] == 1
+ pytest.raises(KeyError, self.adjview.__getitem__, 4)
+
+ def test_copy(self):
+ avcopy = self.adjview.copy()
+ assert avcopy[0] == self.adjview[0]
+ assert avcopy[0] is not self.adjview[0]
+
+ avcopy[2][3]["ht"] = 4
+ assert avcopy[2] != self.adjview[2]
+ self.adjview[2][3]["ht"] = 4
+ assert avcopy[2] == self.adjview[2]
+ del self.adjview[2][3]["ht"]
+
+ assert not hasattr(self.adjview, "__setitem__")
+
+ def test_items(self):
+ view_items = sorted((n, dict(d)) for n, d in self.adjview.items())
+ assert view_items == sorted(self.adj.items())
+
+ def test_str(self):
+ out = str(dict(self.adj))
+ assert str(self.adjview) == out
+
+ def test_repr(self):
+ out = self.adjview.__class__.__name__ + "(" + str(self.adj) + ")"
+ assert repr(self.adjview) == out
+
+
+class TestMultiAdjacencyView(TestAdjacencyView):
+ # node->nbr->key->data
+ def setup_method(self):
+ dd = {"color": "blue", "weight": 1.2}
+ self.kd = {0: dd, 1: {}, 2: {"color": 1}}
+ self.nd = {3: self.kd, 0: {3: dd}, 1: {0: {}}, 2: {3: {"color": 1}}}
+ self.adj = {3: self.nd, 0: {3: {3: dd}}, 1: {}, 2: {3: {8: {}}}}
+ self.adjview = nx.classes.coreviews.MultiAdjacencyView(self.adj)
+
+ def test_getitem(self):
+ assert self.adjview[1] is not self.adj[1]
+ assert self.adjview[3][0][3] is self.adjview[0][3][3]
+ assert self.adjview[3][2][3]["color"] == 1
+ pytest.raises(KeyError, self.adjview.__getitem__, 4)
+
+ def test_copy(self):
+ avcopy = self.adjview.copy()
+ assert avcopy[0] == self.adjview[0]
+ assert avcopy[0] is not self.adjview[0]
+
+ avcopy[2][3][8]["ht"] = 4
+ assert avcopy[2] != self.adjview[2]
+ self.adjview[2][3][8]["ht"] = 4
+ assert avcopy[2] == self.adjview[2]
+ del self.adjview[2][3][8]["ht"]
+
+ assert not hasattr(self.adjview, "__setitem__")
+
+
+class TestUnionAtlas:
+ # node->data
+ def setup_method(self):
+ self.s = {0: {"color": "blue", "weight": 1.2}, 1: {}, 2: {"color": 1}}
+ self.p = {3: {"color": "blue", "weight": 1.2}, 4: {}, 2: {"watch": 2}}
+ self.av = nx.classes.coreviews.UnionAtlas(self.s, self.p)
+
+ def test_pickle(self):
+ view = self.av
+ pview = pickle.loads(pickle.dumps(view, -1))
+ assert view == pview
+ assert view.__slots__ == pview.__slots__
+
+ def test_len(self):
+ assert len(self.av) == len(self.s.keys() | self.p.keys()) == 5
+
+ def test_iter(self):
+ assert set(self.av) == set(self.s) | set(self.p)
+
+ def test_getitem(self):
+ assert self.av[0] is self.s[0]
+ assert self.av[4] is self.p[4]
+ assert self.av[2]["color"] == 1
+ pytest.raises(KeyError, self.av[2].__getitem__, "watch")
+ pytest.raises(KeyError, self.av.__getitem__, 8)
+
+ def test_copy(self):
+ avcopy = self.av.copy()
+ assert avcopy[0] == self.av[0]
+ assert avcopy[0] is not self.av[0]
+ assert avcopy is not self.av
+ avcopy[5] = {}
+ assert avcopy != self.av
+
+ avcopy[0]["ht"] = 4
+ assert avcopy[0] != self.av[0]
+ self.av[0]["ht"] = 4
+ assert avcopy[0] == self.av[0]
+ del self.av[0]["ht"]
+
+ assert not hasattr(self.av, "__setitem__")
+
+ def test_items(self):
+ expected = dict(self.p.items())
+ expected.update(self.s)
+ assert sorted(self.av.items()) == sorted(expected.items())
+
+ def test_str(self):
+ out = str(dict(self.av))
+ assert str(self.av) == out
+
+ def test_repr(self):
+ out = f"{self.av.__class__.__name__}({self.s}, {self.p})"
+ assert repr(self.av) == out
+
+
+class TestUnionAdjacency:
+ # node->nbr->data
+ def setup_method(self):
+ dd = {"color": "blue", "weight": 1.2}
+ self.nd = {0: dd, 1: {}, 2: {"color": 1}}
+ self.s = {3: self.nd, 0: {}, 1: {}, 2: {3: {"color": 1}}}
+ self.p = {3: {}, 0: {3: dd}, 1: {0: {}}, 2: {1: {"color": 1}}}
+ self.adjview = nx.classes.coreviews.UnionAdjacency(self.s, self.p)
+
+ def test_pickle(self):
+ view = self.adjview
+ pview = pickle.loads(pickle.dumps(view, -1))
+ assert view == pview
+ assert view.__slots__ == pview.__slots__
+
+ def test_len(self):
+ assert len(self.adjview) == len(self.s)
+
+ def test_iter(self):
+ assert sorted(self.adjview) == sorted(self.s)
+
+ def test_getitem(self):
+ assert self.adjview[1] is not self.s[1]
+ assert self.adjview[3][0] is self.adjview[0][3]
+ assert self.adjview[2][3]["color"] == 1
+ pytest.raises(KeyError, self.adjview.__getitem__, 4)
+
+ def test_copy(self):
+ avcopy = self.adjview.copy()
+ assert avcopy[0] == self.adjview[0]
+ assert avcopy[0] is not self.adjview[0]
+
+ avcopy[2][3]["ht"] = 4
+ assert avcopy[2] != self.adjview[2]
+ self.adjview[2][3]["ht"] = 4
+ assert avcopy[2] == self.adjview[2]
+ del self.adjview[2][3]["ht"]
+
+ assert not hasattr(self.adjview, "__setitem__")
+
+ def test_str(self):
+ out = str(dict(self.adjview))
+ assert str(self.adjview) == out
+
+ def test_repr(self):
+ clsname = self.adjview.__class__.__name__
+ out = f"{clsname}({self.s}, {self.p})"
+ assert repr(self.adjview) == out
+
+
+class TestUnionMultiInner(TestUnionAdjacency):
+ # nbr->key->data
+ def setup_method(self):
+ dd = {"color": "blue", "weight": 1.2}
+ self.kd = {7: {}, "ekey": {}, 9: {"color": 1}}
+ self.s = {3: self.kd, 0: {7: dd}, 1: {}, 2: {"key": {"color": 1}}}
+ self.p = {3: {}, 0: {3: dd}, 1: {}, 2: {1: {"span": 2}}}
+ self.adjview = nx.classes.coreviews.UnionMultiInner(self.s, self.p)
+
+ def test_len(self):
+ assert len(self.adjview) == len(self.s.keys() | self.p.keys()) == 4
+
+ def test_getitem(self):
+ assert self.adjview[1] is not self.s[1]
+ assert self.adjview[0][7] is self.adjview[0][3]
+ assert self.adjview[2]["key"]["color"] == 1
+ assert self.adjview[2][1]["span"] == 2
+ pytest.raises(KeyError, self.adjview.__getitem__, 4)
+ pytest.raises(KeyError, self.adjview[1].__getitem__, "key")
+
+ def test_copy(self):
+ avcopy = self.adjview.copy()
+ assert avcopy[0] == self.adjview[0]
+ assert avcopy[0] is not self.adjview[0]
+
+ avcopy[2][1]["width"] = 8
+ assert avcopy[2] != self.adjview[2]
+ self.adjview[2][1]["width"] = 8
+ assert avcopy[2] == self.adjview[2]
+ del self.adjview[2][1]["width"]
+
+ assert not hasattr(self.adjview, "__setitem__")
+ assert hasattr(avcopy, "__setitem__")
+
+
+class TestUnionMultiAdjacency(TestUnionAdjacency):
+ # node->nbr->key->data
+ def setup_method(self):
+ dd = {"color": "blue", "weight": 1.2}
+ self.kd = {7: {}, 8: {}, 9: {"color": 1}}
+ self.nd = {3: self.kd, 0: {9: dd}, 1: {8: {}}, 2: {9: {"color": 1}}}
+ self.s = {3: self.nd, 0: {3: {7: dd}}, 1: {}, 2: {3: {8: {}}}}
+ self.p = {3: {}, 0: {3: {9: dd}}, 1: {}, 2: {1: {8: {}}}}
+ self.adjview = nx.classes.coreviews.UnionMultiAdjacency(self.s, self.p)
+
+ def test_getitem(self):
+ assert self.adjview[1] is not self.s[1]
+ assert self.adjview[3][0][9] is self.adjview[0][3][9]
+ assert self.adjview[3][2][9]["color"] == 1
+ pytest.raises(KeyError, self.adjview.__getitem__, 4)
+
+ def test_copy(self):
+ avcopy = self.adjview.copy()
+ assert avcopy[0] == self.adjview[0]
+ assert avcopy[0] is not self.adjview[0]
+
+ avcopy[2][3][8]["ht"] = 4
+ assert avcopy[2] != self.adjview[2]
+ self.adjview[2][3][8]["ht"] = 4
+ assert avcopy[2] == self.adjview[2]
+ del self.adjview[2][3][8]["ht"]
+
+ assert not hasattr(self.adjview, "__setitem__")
+ assert hasattr(avcopy, "__setitem__")
+
+
+class TestFilteredGraphs:
+ def setup_method(self):
+ self.Graphs = [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
+
+ def test_hide_show_nodes(self):
+ SubGraph = nx.subgraph_view
+ for Graph in self.Graphs:
+ G = nx.path_graph(4, Graph)
+ SG = G.subgraph([2, 3])
+ RG = SubGraph(G, filter_node=nx.filters.hide_nodes([0, 1]))
+ assert SG.nodes == RG.nodes
+ assert SG.edges == RG.edges
+ SGC = SG.copy()
+ RGC = RG.copy()
+ assert SGC.nodes == RGC.nodes
+ assert SGC.edges == RGC.edges
+
+ def test_str_repr(self):
+ SubGraph = nx.subgraph_view
+ for Graph in self.Graphs:
+ G = nx.path_graph(4, Graph)
+ SG = G.subgraph([2, 3])
+ RG = SubGraph(G, filter_node=nx.filters.hide_nodes([0, 1]))
+ str(SG.adj)
+ str(RG.adj)
+ repr(SG.adj)
+ repr(RG.adj)
+ str(SG.adj[2])
+ str(RG.adj[2])
+ repr(SG.adj[2])
+ repr(RG.adj[2])
+
+ def test_copy(self):
+ SubGraph = nx.subgraph_view
+ for Graph in self.Graphs:
+ G = nx.path_graph(4, Graph)
+ SG = G.subgraph([2, 3])
+ RG = SubGraph(G, filter_node=nx.filters.hide_nodes([0, 1]))
+ RsG = SubGraph(G, filter_node=nx.filters.show_nodes([2, 3]))
+ assert G.adj.copy() == G.adj
+ assert G.adj[2].copy() == G.adj[2]
+ assert SG.adj.copy() == SG.adj
+ assert SG.adj[2].copy() == SG.adj[2]
+ assert RG.adj.copy() == RG.adj
+ assert RG.adj[2].copy() == RG.adj[2]
+ assert RsG.adj.copy() == RsG.adj
+ assert RsG.adj[2].copy() == RsG.adj[2]
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_digraph.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_digraph.py
new file mode 100644
index 00000000..b9972f9a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_digraph.py
@@ -0,0 +1,331 @@
+import pytest
+
+import networkx as nx
+from networkx.utils import nodes_equal
+
+from .test_graph import BaseAttrGraphTester, BaseGraphTester
+from .test_graph import TestEdgeSubgraph as _TestGraphEdgeSubgraph
+from .test_graph import TestGraph as _TestGraph
+
+
+class BaseDiGraphTester(BaseGraphTester):
+ def test_has_successor(self):
+ G = self.K3
+ assert G.has_successor(0, 1)
+ assert not G.has_successor(0, -1)
+
+ def test_successors(self):
+ G = self.K3
+ assert sorted(G.successors(0)) == [1, 2]
+ with pytest.raises(nx.NetworkXError):
+ G.successors(-1)
+
+ def test_has_predecessor(self):
+ G = self.K3
+ assert G.has_predecessor(0, 1)
+ assert not G.has_predecessor(0, -1)
+
+ def test_predecessors(self):
+ G = self.K3
+ assert sorted(G.predecessors(0)) == [1, 2]
+ with pytest.raises(nx.NetworkXError):
+ G.predecessors(-1)
+
+ def test_edges(self):
+ G = self.K3
+ assert sorted(G.edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
+ assert sorted(G.edges(0)) == [(0, 1), (0, 2)]
+ assert sorted(G.edges([0, 1])) == [(0, 1), (0, 2), (1, 0), (1, 2)]
+ with pytest.raises(nx.NetworkXError):
+ G.edges(-1)
+
+ def test_out_edges(self):
+ G = self.K3
+ assert sorted(G.out_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
+ assert sorted(G.out_edges(0)) == [(0, 1), (0, 2)]
+ with pytest.raises(nx.NetworkXError):
+ G.out_edges(-1)
+
+ def test_out_edges_dir(self):
+ G = self.P3
+ assert sorted(G.out_edges()) == [(0, 1), (1, 2)]
+ assert sorted(G.out_edges(0)) == [(0, 1)]
+ assert sorted(G.out_edges(2)) == []
+
+ def test_out_edges_data(self):
+ G = nx.DiGraph([(0, 1, {"data": 0}), (1, 0, {})])
+ assert sorted(G.out_edges(data=True)) == [(0, 1, {"data": 0}), (1, 0, {})]
+ assert sorted(G.out_edges(0, data=True)) == [(0, 1, {"data": 0})]
+ assert sorted(G.out_edges(data="data")) == [(0, 1, 0), (1, 0, None)]
+ assert sorted(G.out_edges(0, data="data")) == [(0, 1, 0)]
+
+ def test_in_edges_dir(self):
+ G = self.P3
+ assert sorted(G.in_edges()) == [(0, 1), (1, 2)]
+ assert sorted(G.in_edges(0)) == []
+ assert sorted(G.in_edges(2)) == [(1, 2)]
+
+ def test_in_edges_data(self):
+ G = nx.DiGraph([(0, 1, {"data": 0}), (1, 0, {})])
+ assert sorted(G.in_edges(data=True)) == [(0, 1, {"data": 0}), (1, 0, {})]
+ assert sorted(G.in_edges(1, data=True)) == [(0, 1, {"data": 0})]
+ assert sorted(G.in_edges(data="data")) == [(0, 1, 0), (1, 0, None)]
+ assert sorted(G.in_edges(1, data="data")) == [(0, 1, 0)]
+
+ def test_degree(self):
+ G = self.K3
+ assert sorted(G.degree()) == [(0, 4), (1, 4), (2, 4)]
+ assert dict(G.degree()) == {0: 4, 1: 4, 2: 4}
+ assert G.degree(0) == 4
+ assert list(G.degree(iter([0]))) == [(0, 4)] # run through iterator
+
+ def test_in_degree(self):
+ G = self.K3
+ assert sorted(G.in_degree()) == [(0, 2), (1, 2), (2, 2)]
+ assert dict(G.in_degree()) == {0: 2, 1: 2, 2: 2}
+ assert G.in_degree(0) == 2
+ assert list(G.in_degree(iter([0]))) == [(0, 2)] # run through iterator
+
+ def test_out_degree(self):
+ G = self.K3
+ assert sorted(G.out_degree()) == [(0, 2), (1, 2), (2, 2)]
+ assert dict(G.out_degree()) == {0: 2, 1: 2, 2: 2}
+ assert G.out_degree(0) == 2
+ assert list(G.out_degree(iter([0]))) == [(0, 2)]
+
+ def test_size(self):
+ G = self.K3
+ assert G.size() == 6
+ assert G.number_of_edges() == 6
+
+ def test_to_undirected_reciprocal(self):
+ G = self.Graph()
+ G.add_edge(1, 2)
+ assert G.to_undirected().has_edge(1, 2)
+ assert not G.to_undirected(reciprocal=True).has_edge(1, 2)
+ G.add_edge(2, 1)
+ assert G.to_undirected(reciprocal=True).has_edge(1, 2)
+
+ def test_reverse_copy(self):
+ G = nx.DiGraph([(0, 1), (1, 2)])
+ R = G.reverse()
+ assert sorted(R.edges()) == [(1, 0), (2, 1)]
+ R.remove_edge(1, 0)
+ assert sorted(R.edges()) == [(2, 1)]
+ assert sorted(G.edges()) == [(0, 1), (1, 2)]
+
+ def test_reverse_nocopy(self):
+ G = nx.DiGraph([(0, 1), (1, 2)])
+ R = G.reverse(copy=False)
+ assert sorted(R.edges()) == [(1, 0), (2, 1)]
+ with pytest.raises(nx.NetworkXError):
+ R.remove_edge(1, 0)
+
+ def test_reverse_hashable(self):
+ class Foo:
+ pass
+
+ x = Foo()
+ y = Foo()
+ G = nx.DiGraph()
+ G.add_edge(x, y)
+ assert nodes_equal(G.nodes(), G.reverse().nodes())
+ assert [(y, x)] == list(G.reverse().edges())
+
+ def test_di_cache_reset(self):
+ G = self.K3.copy()
+ old_succ = G.succ
+ assert id(G.succ) == id(old_succ)
+ old_adj = G.adj
+ assert id(G.adj) == id(old_adj)
+
+ G._succ = {}
+ assert id(G.succ) != id(old_succ)
+ assert id(G.adj) != id(old_adj)
+
+ old_pred = G.pred
+ assert id(G.pred) == id(old_pred)
+ G._pred = {}
+ assert id(G.pred) != id(old_pred)
+
+ def test_di_attributes_cached(self):
+ G = self.K3.copy()
+ assert id(G.in_edges) == id(G.in_edges)
+ assert id(G.out_edges) == id(G.out_edges)
+ assert id(G.in_degree) == id(G.in_degree)
+ assert id(G.out_degree) == id(G.out_degree)
+ assert id(G.succ) == id(G.succ)
+ assert id(G.pred) == id(G.pred)
+
+
+class BaseAttrDiGraphTester(BaseDiGraphTester, BaseAttrGraphTester):
+ def test_edges_data(self):
+ G = self.K3
+ all_edges = [
+ (0, 1, {}),
+ (0, 2, {}),
+ (1, 0, {}),
+ (1, 2, {}),
+ (2, 0, {}),
+ (2, 1, {}),
+ ]
+ assert sorted(G.edges(data=True)) == all_edges
+ assert sorted(G.edges(0, data=True)) == all_edges[:2]
+ assert sorted(G.edges([0, 1], data=True)) == all_edges[:4]
+ with pytest.raises(nx.NetworkXError):
+ G.edges(-1, True)
+
+ def test_in_degree_weighted(self):
+ G = self.K3.copy()
+ G.add_edge(0, 1, weight=0.3, other=1.2)
+ assert sorted(G.in_degree(weight="weight")) == [(0, 2), (1, 1.3), (2, 2)]
+ assert dict(G.in_degree(weight="weight")) == {0: 2, 1: 1.3, 2: 2}
+ assert G.in_degree(1, weight="weight") == 1.3
+ assert sorted(G.in_degree(weight="other")) == [(0, 2), (1, 2.2), (2, 2)]
+ assert dict(G.in_degree(weight="other")) == {0: 2, 1: 2.2, 2: 2}
+ assert G.in_degree(1, weight="other") == 2.2
+ assert list(G.in_degree(iter([1]), weight="other")) == [(1, 2.2)]
+
+ def test_out_degree_weighted(self):
+ G = self.K3.copy()
+ G.add_edge(0, 1, weight=0.3, other=1.2)
+ assert sorted(G.out_degree(weight="weight")) == [(0, 1.3), (1, 2), (2, 2)]
+ assert dict(G.out_degree(weight="weight")) == {0: 1.3, 1: 2, 2: 2}
+ assert G.out_degree(0, weight="weight") == 1.3
+ assert sorted(G.out_degree(weight="other")) == [(0, 2.2), (1, 2), (2, 2)]
+ assert dict(G.out_degree(weight="other")) == {0: 2.2, 1: 2, 2: 2}
+ assert G.out_degree(0, weight="other") == 2.2
+ assert list(G.out_degree(iter([0]), weight="other")) == [(0, 2.2)]
+
+
+class TestDiGraph(BaseAttrDiGraphTester, _TestGraph):
+ """Tests specific to dict-of-dict-of-dict digraph data structure"""
+
+ def setup_method(self):
+ self.Graph = nx.DiGraph
+ # build dict-of-dict-of-dict K3
+ ed1, ed2, ed3, ed4, ed5, ed6 = ({}, {}, {}, {}, {}, {})
+ self.k3adj = {0: {1: ed1, 2: ed2}, 1: {0: ed3, 2: ed4}, 2: {0: ed5, 1: ed6}}
+ self.k3edges = [(0, 1), (0, 2), (1, 2)]
+ self.k3nodes = [0, 1, 2]
+ self.K3 = self.Graph()
+ self.K3._succ = self.k3adj # K3._adj is synced with K3._succ
+ self.K3._pred = {0: {1: ed3, 2: ed5}, 1: {0: ed1, 2: ed6}, 2: {0: ed2, 1: ed4}}
+ self.K3._node = {}
+ self.K3._node[0] = {}
+ self.K3._node[1] = {}
+ self.K3._node[2] = {}
+
+ ed1, ed2 = ({}, {})
+ self.P3 = self.Graph()
+ self.P3._succ = {0: {1: ed1}, 1: {2: ed2}, 2: {}}
+ self.P3._pred = {0: {}, 1: {0: ed1}, 2: {1: ed2}}
+ # P3._adj is synced with P3._succ
+ self.P3._node = {}
+ self.P3._node[0] = {}
+ self.P3._node[1] = {}
+ self.P3._node[2] = {}
+
+ def test_data_input(self):
+ G = self.Graph({1: [2], 2: [1]}, name="test")
+ assert G.name == "test"
+ assert sorted(G.adj.items()) == [(1, {2: {}}), (2, {1: {}})]
+ assert sorted(G.succ.items()) == [(1, {2: {}}), (2, {1: {}})]
+ assert sorted(G.pred.items()) == [(1, {2: {}}), (2, {1: {}})]
+
+ def test_add_edge(self):
+ G = self.Graph()
+ G.add_edge(0, 1)
+ assert G.adj == {0: {1: {}}, 1: {}}
+ assert G.succ == {0: {1: {}}, 1: {}}
+ assert G.pred == {0: {}, 1: {0: {}}}
+ G = self.Graph()
+ G.add_edge(*(0, 1))
+ assert G.adj == {0: {1: {}}, 1: {}}
+ assert G.succ == {0: {1: {}}, 1: {}}
+ assert G.pred == {0: {}, 1: {0: {}}}
+ with pytest.raises(ValueError, match="None cannot be a node"):
+ G.add_edge(None, 3)
+
+ def test_add_edges_from(self):
+ G = self.Graph()
+ G.add_edges_from([(0, 1), (0, 2, {"data": 3})], data=2)
+ assert G.adj == {0: {1: {"data": 2}, 2: {"data": 3}}, 1: {}, 2: {}}
+ assert G.succ == {0: {1: {"data": 2}, 2: {"data": 3}}, 1: {}, 2: {}}
+ assert G.pred == {0: {}, 1: {0: {"data": 2}}, 2: {0: {"data": 3}}}
+
+ with pytest.raises(nx.NetworkXError):
+ G.add_edges_from([(0,)]) # too few in tuple
+ with pytest.raises(nx.NetworkXError):
+ G.add_edges_from([(0, 1, 2, 3)]) # too many in tuple
+ with pytest.raises(TypeError):
+ G.add_edges_from([0]) # not a tuple
+ with pytest.raises(ValueError, match="None cannot be a node"):
+ G.add_edges_from([(None, 3), (3, 2)])
+
+ def test_remove_edge(self):
+ G = self.K3.copy()
+ G.remove_edge(0, 1)
+ assert G.succ == {0: {2: {}}, 1: {0: {}, 2: {}}, 2: {0: {}, 1: {}}}
+ assert G.pred == {0: {1: {}, 2: {}}, 1: {2: {}}, 2: {0: {}, 1: {}}}
+ with pytest.raises(nx.NetworkXError):
+ G.remove_edge(-1, 0)
+
+ def test_remove_edges_from(self):
+ G = self.K3.copy()
+ G.remove_edges_from([(0, 1)])
+ assert G.succ == {0: {2: {}}, 1: {0: {}, 2: {}}, 2: {0: {}, 1: {}}}
+ assert G.pred == {0: {1: {}, 2: {}}, 1: {2: {}}, 2: {0: {}, 1: {}}}
+ G.remove_edges_from([(0, 0)]) # silent fail
+
+ def test_clear(self):
+ G = self.K3
+ G.graph["name"] = "K3"
+ G.clear()
+ assert list(G.nodes) == []
+ assert G.succ == {}
+ assert G.pred == {}
+ assert G.graph == {}
+
+ def test_clear_edges(self):
+ G = self.K3
+ G.graph["name"] = "K3"
+ nodes = list(G.nodes)
+ G.clear_edges()
+ assert list(G.nodes) == nodes
+ expected = {0: {}, 1: {}, 2: {}}
+ assert G.succ == expected
+ assert G.pred == expected
+ assert list(G.edges) == []
+ assert G.graph["name"] == "K3"
+
+
+class TestEdgeSubgraph(_TestGraphEdgeSubgraph):
+ """Unit tests for the :meth:`DiGraph.edge_subgraph` method."""
+
+ def setup_method(self):
+ # Create a doubly-linked path graph on five nodes.
+ G = nx.DiGraph(nx.path_graph(5))
+ # Add some node, edge, and graph attributes.
+ for i in range(5):
+ G.nodes[i]["name"] = f"node{i}"
+ G.edges[0, 1]["name"] = "edge01"
+ G.edges[3, 4]["name"] = "edge34"
+ G.graph["name"] = "graph"
+ # Get the subgraph induced by the first and last edges.
+ self.G = G
+ self.H = G.edge_subgraph([(0, 1), (3, 4)])
+
+ def test_pred_succ(self):
+ """Test that nodes are added to predecessors and successors.
+
+ For more information, see GitHub issue #2370.
+
+ """
+ G = nx.DiGraph()
+ G.add_edge(0, 1)
+ H = G.edge_subgraph([(0, 1)])
+ assert list(H.predecessors(0)) == []
+ assert list(H.successors(0)) == [1]
+ assert list(H.predecessors(1)) == [0]
+ assert list(H.successors(1)) == []
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_digraph_historical.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_digraph_historical.py
new file mode 100644
index 00000000..4f2b1da9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_digraph_historical.py
@@ -0,0 +1,111 @@
+"""Original NetworkX graph tests"""
+
+import pytest
+
+import networkx
+import networkx as nx
+
+from .historical_tests import HistoricalTests
+
+
+class TestDiGraphHistorical(HistoricalTests):
+ @classmethod
+ def setup_class(cls):
+ HistoricalTests.setup_class()
+ cls.G = nx.DiGraph
+
+ def test_in_degree(self):
+ G = self.G()
+ G.add_nodes_from("GJK")
+ G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("B", "C"), ("C", "D")])
+
+ assert sorted(d for n, d in G.in_degree()) == [0, 0, 0, 0, 1, 2, 2]
+ assert dict(G.in_degree()) == {
+ "A": 0,
+ "C": 2,
+ "B": 1,
+ "D": 2,
+ "G": 0,
+ "K": 0,
+ "J": 0,
+ }
+
+ def test_out_degree(self):
+ G = self.G()
+ G.add_nodes_from("GJK")
+ G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("B", "C"), ("C", "D")])
+ assert sorted(v for k, v in G.in_degree()) == [0, 0, 0, 0, 1, 2, 2]
+ assert dict(G.out_degree()) == {
+ "A": 2,
+ "C": 1,
+ "B": 2,
+ "D": 0,
+ "G": 0,
+ "K": 0,
+ "J": 0,
+ }
+
+ def test_degree_digraph(self):
+ H = nx.DiGraph()
+ H.add_edges_from([(1, 24), (1, 2)])
+ assert sorted(d for n, d in H.in_degree([1, 24])) == [0, 1]
+ assert sorted(d for n, d in H.out_degree([1, 24])) == [0, 2]
+ assert sorted(d for n, d in H.degree([1, 24])) == [1, 2]
+
+ def test_neighbors(self):
+ G = self.G()
+ G.add_nodes_from("GJK")
+ G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("B", "C"), ("C", "D")])
+
+ assert sorted(G.neighbors("C")) == ["D"]
+ assert sorted(G["C"]) == ["D"]
+ assert sorted(G.neighbors("A")) == ["B", "C"]
+ pytest.raises(nx.NetworkXError, G.neighbors, "j")
+ pytest.raises(nx.NetworkXError, G.neighbors, "j")
+
+ def test_successors(self):
+ G = self.G()
+ G.add_nodes_from("GJK")
+ G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("B", "C"), ("C", "D")])
+ assert sorted(G.successors("A")) == ["B", "C"]
+ assert sorted(G.successors("A")) == ["B", "C"]
+ assert sorted(G.successors("G")) == []
+ assert sorted(G.successors("D")) == []
+ assert sorted(G.successors("G")) == []
+ pytest.raises(nx.NetworkXError, G.successors, "j")
+ pytest.raises(nx.NetworkXError, G.successors, "j")
+
+ def test_predecessors(self):
+ G = self.G()
+ G.add_nodes_from("GJK")
+ G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("B", "C"), ("C", "D")])
+ assert sorted(G.predecessors("C")) == ["A", "B"]
+ assert sorted(G.predecessors("C")) == ["A", "B"]
+ assert sorted(G.predecessors("G")) == []
+ assert sorted(G.predecessors("A")) == []
+ assert sorted(G.predecessors("G")) == []
+ assert sorted(G.predecessors("A")) == []
+ assert sorted(G.successors("D")) == []
+
+ pytest.raises(nx.NetworkXError, G.predecessors, "j")
+ pytest.raises(nx.NetworkXError, G.predecessors, "j")
+
+ def test_reverse(self):
+ G = nx.complete_graph(10)
+ H = G.to_directed()
+ HR = H.reverse()
+ assert nx.is_isomorphic(H, HR)
+ assert sorted(H.edges()) == sorted(HR.edges())
+
+ def test_reverse2(self):
+ H = nx.DiGraph()
+ foo = [H.add_edge(u, u + 1) for u in range(5)]
+ HR = H.reverse()
+ for u in range(5):
+ assert HR.has_edge(u + 1, u)
+
+ def test_reverse3(self):
+ H = nx.DiGraph()
+ H.add_nodes_from([1, 2, 3, 4])
+ HR = H.reverse()
+ assert sorted(HR.nodes()) == [1, 2, 3, 4]
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_filters.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_filters.py
new file mode 100644
index 00000000..2da59117
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_filters.py
@@ -0,0 +1,177 @@
+import pytest
+
+import networkx as nx
+
+
+class TestFilterFactory:
+ def test_no_filter(self):
+ nf = nx.filters.no_filter
+ assert nf()
+ assert nf(1)
+ assert nf(2, 1)
+
+ def test_hide_nodes(self):
+ f = nx.classes.filters.hide_nodes([1, 2, 3])
+ assert not f(1)
+ assert not f(2)
+ assert not f(3)
+ assert f(4)
+ assert f(0)
+ assert f("a")
+ pytest.raises(TypeError, f, 1, 2)
+ pytest.raises(TypeError, f)
+
+ def test_show_nodes(self):
+ f = nx.classes.filters.show_nodes([1, 2, 3])
+ assert f(1)
+ assert f(2)
+ assert f(3)
+ assert not f(4)
+ assert not f(0)
+ assert not f("a")
+ pytest.raises(TypeError, f, 1, 2)
+ pytest.raises(TypeError, f)
+
+ def test_hide_edges(self):
+ factory = nx.classes.filters.hide_edges
+ f = factory([(1, 2), (3, 4)])
+ assert not f(1, 2)
+ assert not f(3, 4)
+ assert not f(4, 3)
+ assert f(2, 3)
+ assert f(0, -1)
+ assert f("a", "b")
+ pytest.raises(TypeError, f, 1, 2, 3)
+ pytest.raises(TypeError, f, 1)
+ pytest.raises(TypeError, f)
+ pytest.raises(TypeError, factory, [1, 2, 3])
+ pytest.raises(ValueError, factory, [(1, 2, 3)])
+
+ def test_show_edges(self):
+ factory = nx.classes.filters.show_edges
+ f = factory([(1, 2), (3, 4)])
+ assert f(1, 2)
+ assert f(3, 4)
+ assert f(4, 3)
+ assert not f(2, 3)
+ assert not f(0, -1)
+ assert not f("a", "b")
+ pytest.raises(TypeError, f, 1, 2, 3)
+ pytest.raises(TypeError, f, 1)
+ pytest.raises(TypeError, f)
+ pytest.raises(TypeError, factory, [1, 2, 3])
+ pytest.raises(ValueError, factory, [(1, 2, 3)])
+
+ def test_hide_diedges(self):
+ factory = nx.classes.filters.hide_diedges
+ f = factory([(1, 2), (3, 4)])
+ assert not f(1, 2)
+ assert not f(3, 4)
+ assert f(4, 3)
+ assert f(2, 3)
+ assert f(0, -1)
+ assert f("a", "b")
+ pytest.raises(TypeError, f, 1, 2, 3)
+ pytest.raises(TypeError, f, 1)
+ pytest.raises(TypeError, f)
+ pytest.raises(TypeError, factory, [1, 2, 3])
+ pytest.raises(ValueError, factory, [(1, 2, 3)])
+
+ def test_show_diedges(self):
+ factory = nx.classes.filters.show_diedges
+ f = factory([(1, 2), (3, 4)])
+ assert f(1, 2)
+ assert f(3, 4)
+ assert not f(4, 3)
+ assert not f(2, 3)
+ assert not f(0, -1)
+ assert not f("a", "b")
+ pytest.raises(TypeError, f, 1, 2, 3)
+ pytest.raises(TypeError, f, 1)
+ pytest.raises(TypeError, f)
+ pytest.raises(TypeError, factory, [1, 2, 3])
+ pytest.raises(ValueError, factory, [(1, 2, 3)])
+
+ def test_hide_multiedges(self):
+ factory = nx.classes.filters.hide_multiedges
+ f = factory([(1, 2, 0), (3, 4, 1), (1, 2, 1)])
+ assert not f(1, 2, 0)
+ assert not f(1, 2, 1)
+ assert f(1, 2, 2)
+ assert f(3, 4, 0)
+ assert not f(3, 4, 1)
+ assert not f(4, 3, 1)
+ assert f(4, 3, 0)
+ assert f(2, 3, 0)
+ assert f(0, -1, 0)
+ assert f("a", "b", 0)
+ pytest.raises(TypeError, f, 1, 2, 3, 4)
+ pytest.raises(TypeError, f, 1, 2)
+ pytest.raises(TypeError, f, 1)
+ pytest.raises(TypeError, f)
+ pytest.raises(TypeError, factory, [1, 2, 3])
+ pytest.raises(ValueError, factory, [(1, 2)])
+ pytest.raises(ValueError, factory, [(1, 2, 3, 4)])
+
+ def test_show_multiedges(self):
+ factory = nx.classes.filters.show_multiedges
+ f = factory([(1, 2, 0), (3, 4, 1), (1, 2, 1)])
+ assert f(1, 2, 0)
+ assert f(1, 2, 1)
+ assert not f(1, 2, 2)
+ assert not f(3, 4, 0)
+ assert f(3, 4, 1)
+ assert f(4, 3, 1)
+ assert not f(4, 3, 0)
+ assert not f(2, 3, 0)
+ assert not f(0, -1, 0)
+ assert not f("a", "b", 0)
+ pytest.raises(TypeError, f, 1, 2, 3, 4)
+ pytest.raises(TypeError, f, 1, 2)
+ pytest.raises(TypeError, f, 1)
+ pytest.raises(TypeError, f)
+ pytest.raises(TypeError, factory, [1, 2, 3])
+ pytest.raises(ValueError, factory, [(1, 2)])
+ pytest.raises(ValueError, factory, [(1, 2, 3, 4)])
+
+ def test_hide_multidiedges(self):
+ factory = nx.classes.filters.hide_multidiedges
+ f = factory([(1, 2, 0), (3, 4, 1), (1, 2, 1)])
+ assert not f(1, 2, 0)
+ assert not f(1, 2, 1)
+ assert f(1, 2, 2)
+ assert f(3, 4, 0)
+ assert not f(3, 4, 1)
+ assert f(4, 3, 1)
+ assert f(4, 3, 0)
+ assert f(2, 3, 0)
+ assert f(0, -1, 0)
+ assert f("a", "b", 0)
+ pytest.raises(TypeError, f, 1, 2, 3, 4)
+ pytest.raises(TypeError, f, 1, 2)
+ pytest.raises(TypeError, f, 1)
+ pytest.raises(TypeError, f)
+ pytest.raises(TypeError, factory, [1, 2, 3])
+ pytest.raises(ValueError, factory, [(1, 2)])
+ pytest.raises(ValueError, factory, [(1, 2, 3, 4)])
+
+ def test_show_multidiedges(self):
+ factory = nx.classes.filters.show_multidiedges
+ f = factory([(1, 2, 0), (3, 4, 1), (1, 2, 1)])
+ assert f(1, 2, 0)
+ assert f(1, 2, 1)
+ assert not f(1, 2, 2)
+ assert not f(3, 4, 0)
+ assert f(3, 4, 1)
+ assert not f(4, 3, 1)
+ assert not f(4, 3, 0)
+ assert not f(2, 3, 0)
+ assert not f(0, -1, 0)
+ assert not f("a", "b", 0)
+ pytest.raises(TypeError, f, 1, 2, 3, 4)
+ pytest.raises(TypeError, f, 1, 2)
+ pytest.raises(TypeError, f, 1)
+ pytest.raises(TypeError, f)
+ pytest.raises(TypeError, factory, [1, 2, 3])
+ pytest.raises(ValueError, factory, [(1, 2)])
+ pytest.raises(ValueError, factory, [(1, 2, 3, 4)])
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_function.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_function.py
new file mode 100644
index 00000000..f86890dd
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_function.py
@@ -0,0 +1,1035 @@
+import random
+
+import pytest
+
+import networkx as nx
+from networkx.utils import edges_equal, nodes_equal
+
+
+def test_degree_histogram_empty():
+ G = nx.Graph()
+ assert nx.degree_histogram(G) == []
+
+
+class TestFunction:
+ def setup_method(self):
+ self.G = nx.Graph({0: [1, 2, 3], 1: [1, 2, 0], 4: []}, name="Test")
+ self.Gdegree = {0: 3, 1: 2, 2: 2, 3: 1, 4: 0}
+ self.Gnodes = list(range(5))
+ self.Gedges = [(0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2)]
+ self.DG = nx.DiGraph({0: [1, 2, 3], 1: [1, 2, 0], 4: []})
+ self.DGin_degree = {0: 1, 1: 2, 2: 2, 3: 1, 4: 0}
+ self.DGout_degree = {0: 3, 1: 3, 2: 0, 3: 0, 4: 0}
+ self.DGnodes = list(range(5))
+ self.DGedges = [(0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2)]
+
+ def test_nodes(self):
+ assert nodes_equal(self.G.nodes(), list(nx.nodes(self.G)))
+ assert nodes_equal(self.DG.nodes(), list(nx.nodes(self.DG)))
+
+ def test_edges(self):
+ assert edges_equal(self.G.edges(), list(nx.edges(self.G)))
+ assert sorted(self.DG.edges()) == sorted(nx.edges(self.DG))
+ assert edges_equal(
+ self.G.edges(nbunch=[0, 1, 3]), list(nx.edges(self.G, nbunch=[0, 1, 3]))
+ )
+ assert sorted(self.DG.edges(nbunch=[0, 1, 3])) == sorted(
+ nx.edges(self.DG, nbunch=[0, 1, 3])
+ )
+
+ def test_degree(self):
+ assert edges_equal(self.G.degree(), list(nx.degree(self.G)))
+ assert sorted(self.DG.degree()) == sorted(nx.degree(self.DG))
+ assert edges_equal(
+ self.G.degree(nbunch=[0, 1]), list(nx.degree(self.G, nbunch=[0, 1]))
+ )
+ assert sorted(self.DG.degree(nbunch=[0, 1])) == sorted(
+ nx.degree(self.DG, nbunch=[0, 1])
+ )
+ assert edges_equal(
+ self.G.degree(weight="weight"), list(nx.degree(self.G, weight="weight"))
+ )
+ assert sorted(self.DG.degree(weight="weight")) == sorted(
+ nx.degree(self.DG, weight="weight")
+ )
+
+ def test_neighbors(self):
+ assert list(self.G.neighbors(1)) == list(nx.neighbors(self.G, 1))
+ assert list(self.DG.neighbors(1)) == list(nx.neighbors(self.DG, 1))
+
+ def test_number_of_nodes(self):
+ assert self.G.number_of_nodes() == nx.number_of_nodes(self.G)
+ assert self.DG.number_of_nodes() == nx.number_of_nodes(self.DG)
+
+ def test_number_of_edges(self):
+ assert self.G.number_of_edges() == nx.number_of_edges(self.G)
+ assert self.DG.number_of_edges() == nx.number_of_edges(self.DG)
+
+ def test_is_directed(self):
+ assert self.G.is_directed() == nx.is_directed(self.G)
+ assert self.DG.is_directed() == nx.is_directed(self.DG)
+
+ def test_add_star(self):
+ G = self.G.copy()
+ nlist = [12, 13, 14, 15]
+ nx.add_star(G, nlist)
+ assert edges_equal(G.edges(nlist), [(12, 13), (12, 14), (12, 15)])
+
+ G = self.G.copy()
+ nx.add_star(G, nlist, weight=2.0)
+ assert edges_equal(
+ G.edges(nlist, data=True),
+ [
+ (12, 13, {"weight": 2.0}),
+ (12, 14, {"weight": 2.0}),
+ (12, 15, {"weight": 2.0}),
+ ],
+ )
+
+ G = self.G.copy()
+ nlist = [12]
+ nx.add_star(G, nlist)
+ assert nodes_equal(G, list(self.G) + nlist)
+
+ G = self.G.copy()
+ nlist = []
+ nx.add_star(G, nlist)
+ assert nodes_equal(G.nodes, self.Gnodes)
+ assert edges_equal(G.edges, self.G.edges)
+
+ def test_add_path(self):
+ G = self.G.copy()
+ nlist = [12, 13, 14, 15]
+ nx.add_path(G, nlist)
+ assert edges_equal(G.edges(nlist), [(12, 13), (13, 14), (14, 15)])
+ G = self.G.copy()
+ nx.add_path(G, nlist, weight=2.0)
+ assert edges_equal(
+ G.edges(nlist, data=True),
+ [
+ (12, 13, {"weight": 2.0}),
+ (13, 14, {"weight": 2.0}),
+ (14, 15, {"weight": 2.0}),
+ ],
+ )
+
+ G = self.G.copy()
+ nlist = ["node"]
+ nx.add_path(G, nlist)
+ assert edges_equal(G.edges(nlist), [])
+ assert nodes_equal(G, list(self.G) + ["node"])
+
+ G = self.G.copy()
+ nlist = iter(["node"])
+ nx.add_path(G, nlist)
+ assert edges_equal(G.edges(["node"]), [])
+ assert nodes_equal(G, list(self.G) + ["node"])
+
+ G = self.G.copy()
+ nlist = [12]
+ nx.add_path(G, nlist)
+ assert edges_equal(G.edges(nlist), [])
+ assert nodes_equal(G, list(self.G) + [12])
+
+ G = self.G.copy()
+ nlist = iter([12])
+ nx.add_path(G, nlist)
+ assert edges_equal(G.edges([12]), [])
+ assert nodes_equal(G, list(self.G) + [12])
+
+ G = self.G.copy()
+ nlist = []
+ nx.add_path(G, nlist)
+ assert edges_equal(G.edges, self.G.edges)
+ assert nodes_equal(G, list(self.G))
+
+ G = self.G.copy()
+ nlist = iter([])
+ nx.add_path(G, nlist)
+ assert edges_equal(G.edges, self.G.edges)
+ assert nodes_equal(G, list(self.G))
+
+ def test_add_cycle(self):
+ G = self.G.copy()
+ nlist = [12, 13, 14, 15]
+ oklists = [
+ [(12, 13), (12, 15), (13, 14), (14, 15)],
+ [(12, 13), (13, 14), (14, 15), (15, 12)],
+ ]
+ nx.add_cycle(G, nlist)
+ assert sorted(G.edges(nlist)) in oklists
+ G = self.G.copy()
+ oklists = [
+ [
+ (12, 13, {"weight": 1.0}),
+ (12, 15, {"weight": 1.0}),
+ (13, 14, {"weight": 1.0}),
+ (14, 15, {"weight": 1.0}),
+ ],
+ [
+ (12, 13, {"weight": 1.0}),
+ (13, 14, {"weight": 1.0}),
+ (14, 15, {"weight": 1.0}),
+ (15, 12, {"weight": 1.0}),
+ ],
+ ]
+ nx.add_cycle(G, nlist, weight=1.0)
+ assert sorted(G.edges(nlist, data=True)) in oklists
+
+ G = self.G.copy()
+ nlist = [12]
+ nx.add_cycle(G, nlist)
+ assert nodes_equal(G, list(self.G) + nlist)
+
+ G = self.G.copy()
+ nlist = []
+ nx.add_cycle(G, nlist)
+ assert nodes_equal(G.nodes, self.Gnodes)
+ assert edges_equal(G.edges, self.G.edges)
+
+ def test_subgraph(self):
+ assert (
+ self.G.subgraph([0, 1, 2, 4]).adj == nx.subgraph(self.G, [0, 1, 2, 4]).adj
+ )
+ assert (
+ self.DG.subgraph([0, 1, 2, 4]).adj == nx.subgraph(self.DG, [0, 1, 2, 4]).adj
+ )
+ assert (
+ self.G.subgraph([0, 1, 2, 4]).adj
+ == nx.induced_subgraph(self.G, [0, 1, 2, 4]).adj
+ )
+ assert (
+ self.DG.subgraph([0, 1, 2, 4]).adj
+ == nx.induced_subgraph(self.DG, [0, 1, 2, 4]).adj
+ )
+ # subgraph-subgraph chain is allowed in function interface
+ H = nx.induced_subgraph(self.G.subgraph([0, 1, 2, 4]), [0, 1, 4])
+ assert H._graph is not self.G
+ assert H.adj == self.G.subgraph([0, 1, 4]).adj
+
+ def test_edge_subgraph(self):
+ assert (
+ self.G.edge_subgraph([(1, 2), (0, 3)]).adj
+ == nx.edge_subgraph(self.G, [(1, 2), (0, 3)]).adj
+ )
+ assert (
+ self.DG.edge_subgraph([(1, 2), (0, 3)]).adj
+ == nx.edge_subgraph(self.DG, [(1, 2), (0, 3)]).adj
+ )
+
+ def test_create_empty_copy(self):
+ G = nx.create_empty_copy(self.G, with_data=False)
+ assert nodes_equal(G, list(self.G))
+ assert G.graph == {}
+ assert G._node == {}.fromkeys(self.G.nodes(), {})
+ assert G._adj == {}.fromkeys(self.G.nodes(), {})
+ G = nx.create_empty_copy(self.G)
+ assert nodes_equal(G, list(self.G))
+ assert G.graph == self.G.graph
+ assert G._node == self.G._node
+ assert G._adj == {}.fromkeys(self.G.nodes(), {})
+
+ def test_degree_histogram(self):
+ assert nx.degree_histogram(self.G) == [1, 1, 1, 1, 1]
+
+ def test_density(self):
+ assert nx.density(self.G) == 0.5
+ assert nx.density(self.DG) == 0.3
+ G = nx.Graph()
+ G.add_node(1)
+ assert nx.density(G) == 0.0
+
+ def test_density_selfloop(self):
+ G = nx.Graph()
+ G.add_edge(1, 1)
+ assert nx.density(G) == 0.0
+ G.add_edge(1, 2)
+ assert nx.density(G) == 2.0
+
+ def test_freeze(self):
+ G = nx.freeze(self.G)
+ assert G.frozen
+ pytest.raises(nx.NetworkXError, G.add_node, 1)
+ pytest.raises(nx.NetworkXError, G.add_nodes_from, [1])
+ pytest.raises(nx.NetworkXError, G.remove_node, 1)
+ pytest.raises(nx.NetworkXError, G.remove_nodes_from, [1])
+ pytest.raises(nx.NetworkXError, G.add_edge, 1, 2)
+ pytest.raises(nx.NetworkXError, G.add_edges_from, [(1, 2)])
+ pytest.raises(nx.NetworkXError, G.remove_edge, 1, 2)
+ pytest.raises(nx.NetworkXError, G.remove_edges_from, [(1, 2)])
+ pytest.raises(nx.NetworkXError, G.clear_edges)
+ pytest.raises(nx.NetworkXError, G.clear)
+
+ def test_is_frozen(self):
+ assert not nx.is_frozen(self.G)
+ G = nx.freeze(self.G)
+ assert G.frozen == nx.is_frozen(self.G)
+ assert G.frozen
+
+ def test_node_attributes_are_still_mutable_on_frozen_graph(self):
+ G = nx.freeze(nx.path_graph(3))
+ node = G.nodes[0]
+ node["node_attribute"] = True
+ assert node["node_attribute"] == True
+
+ def test_edge_attributes_are_still_mutable_on_frozen_graph(self):
+ G = nx.freeze(nx.path_graph(3))
+ edge = G.edges[(0, 1)]
+ edge["edge_attribute"] = True
+ assert edge["edge_attribute"] == True
+
+ def test_neighbors_complete_graph(self):
+ graph = nx.complete_graph(100)
+ pop = random.sample(list(graph), 1)
+ nbors = list(nx.neighbors(graph, pop[0]))
+ # should be all the other vertices in the graph
+ assert len(nbors) == len(graph) - 1
+
+ graph = nx.path_graph(100)
+ node = random.sample(list(graph), 1)[0]
+ nbors = list(nx.neighbors(graph, node))
+ # should be all the other vertices in the graph
+ if node != 0 and node != 99:
+ assert len(nbors) == 2
+ else:
+ assert len(nbors) == 1
+
+ # create a star graph with 99 outer nodes
+ graph = nx.star_graph(99)
+ nbors = list(nx.neighbors(graph, 0))
+ assert len(nbors) == 99
+
+ def test_non_neighbors(self):
+ graph = nx.complete_graph(100)
+ pop = random.sample(list(graph), 1)
+ nbors = nx.non_neighbors(graph, pop[0])
+ # should be all the other vertices in the graph
+ assert len(nbors) == 0
+
+ graph = nx.path_graph(100)
+ node = random.sample(list(graph), 1)[0]
+ nbors = nx.non_neighbors(graph, node)
+ # should be all the other vertices in the graph
+ if node != 0 and node != 99:
+ assert len(nbors) == 97
+ else:
+ assert len(nbors) == 98
+
+ # create a star graph with 99 outer nodes
+ graph = nx.star_graph(99)
+ nbors = nx.non_neighbors(graph, 0)
+ assert len(nbors) == 0
+
+ # disconnected graph
+ graph = nx.Graph()
+ graph.add_nodes_from(range(10))
+ nbors = nx.non_neighbors(graph, 0)
+ assert len(nbors) == 9
+
+ def test_non_edges(self):
+ # All possible edges exist
+ graph = nx.complete_graph(5)
+ nedges = list(nx.non_edges(graph))
+ assert len(nedges) == 0
+
+ graph = nx.path_graph(4)
+ expected = [(0, 2), (0, 3), (1, 3)]
+ nedges = list(nx.non_edges(graph))
+ for u, v in expected:
+ assert (u, v) in nedges or (v, u) in nedges
+
+ graph = nx.star_graph(4)
+ expected = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
+ nedges = list(nx.non_edges(graph))
+ for u, v in expected:
+ assert (u, v) in nedges or (v, u) in nedges
+
+ # Directed graphs
+ graph = nx.DiGraph()
+ graph.add_edges_from([(0, 2), (2, 0), (2, 1)])
+ expected = [(0, 1), (1, 0), (1, 2)]
+ nedges = list(nx.non_edges(graph))
+ for e in expected:
+ assert e in nedges
+
+ def test_is_weighted(self):
+ G = nx.Graph()
+ assert not nx.is_weighted(G)
+
+ G = nx.path_graph(4)
+ assert not nx.is_weighted(G)
+ assert not nx.is_weighted(G, (2, 3))
+
+ G.add_node(4)
+ G.add_edge(3, 4, weight=4)
+ assert not nx.is_weighted(G)
+ assert nx.is_weighted(G, (3, 4))
+
+ G = nx.DiGraph()
+ G.add_weighted_edges_from(
+ [
+ ("0", "3", 3),
+ ("0", "1", -5),
+ ("1", "0", -5),
+ ("0", "2", 2),
+ ("1", "2", 4),
+ ("2", "3", 1),
+ ]
+ )
+ assert nx.is_weighted(G)
+ assert nx.is_weighted(G, ("1", "0"))
+
+ G = G.to_undirected()
+ assert nx.is_weighted(G)
+ assert nx.is_weighted(G, ("1", "0"))
+
+ pytest.raises(nx.NetworkXError, nx.is_weighted, G, (1, 2))
+
+ def test_is_negatively_weighted(self):
+ G = nx.Graph()
+ assert not nx.is_negatively_weighted(G)
+
+ G.add_node(1)
+ G.add_nodes_from([2, 3, 4, 5])
+ assert not nx.is_negatively_weighted(G)
+
+ G.add_edge(1, 2, weight=4)
+ assert not nx.is_negatively_weighted(G, (1, 2))
+
+ G.add_edges_from([(1, 3), (2, 4), (2, 6)])
+ G[1][3]["color"] = "blue"
+ assert not nx.is_negatively_weighted(G)
+ assert not nx.is_negatively_weighted(G, (1, 3))
+
+ G[2][4]["weight"] = -2
+ assert nx.is_negatively_weighted(G, (2, 4))
+ assert nx.is_negatively_weighted(G)
+
+ G = nx.DiGraph()
+ G.add_weighted_edges_from(
+ [
+ ("0", "3", 3),
+ ("0", "1", -5),
+ ("1", "0", -2),
+ ("0", "2", 2),
+ ("1", "2", -3),
+ ("2", "3", 1),
+ ]
+ )
+ assert nx.is_negatively_weighted(G)
+ assert not nx.is_negatively_weighted(G, ("0", "3"))
+ assert nx.is_negatively_weighted(G, ("1", "0"))
+
+ pytest.raises(nx.NetworkXError, nx.is_negatively_weighted, G, (1, 4))
+
+
+class TestCommonNeighbors:
+ @classmethod
+ def setup_class(cls):
+ cls.func = staticmethod(nx.common_neighbors)
+
+ def test_func(G, u, v, expected):
+ result = sorted(cls.func(G, u, v))
+ assert result == expected
+
+ cls.test = staticmethod(test_func)
+
+ def test_K5(self):
+ G = nx.complete_graph(5)
+ self.test(G, 0, 1, [2, 3, 4])
+
+ def test_P3(self):
+ G = nx.path_graph(3)
+ self.test(G, 0, 2, [1])
+
+ def test_S4(self):
+ G = nx.star_graph(4)
+ self.test(G, 1, 2, [0])
+
+ def test_digraph(self):
+ with pytest.raises(nx.NetworkXNotImplemented):
+ G = nx.DiGraph()
+ G.add_edges_from([(0, 1), (1, 2)])
+ self.func(G, 0, 2)
+
+ def test_nonexistent_nodes(self):
+ G = nx.complete_graph(5)
+ pytest.raises(nx.NetworkXError, nx.common_neighbors, G, 5, 4)
+ pytest.raises(nx.NetworkXError, nx.common_neighbors, G, 4, 5)
+ pytest.raises(nx.NetworkXError, nx.common_neighbors, G, 5, 6)
+
+ def test_custom1(self):
+ """Case of no common neighbors."""
+ G = nx.Graph()
+ G.add_nodes_from([0, 1])
+ self.test(G, 0, 1, [])
+
+ def test_custom2(self):
+ """Case of equal nodes."""
+ G = nx.complete_graph(4)
+ self.test(G, 0, 0, [1, 2, 3])
+
+
+@pytest.mark.parametrize(
+ "graph_type", (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
+)
+def test_set_node_attributes(graph_type):
+ # Test single value
+ G = nx.path_graph(3, create_using=graph_type)
+ vals = 100
+ attr = "hello"
+ nx.set_node_attributes(G, vals, attr)
+ assert G.nodes[0][attr] == vals
+ assert G.nodes[1][attr] == vals
+ assert G.nodes[2][attr] == vals
+
+ # Test dictionary
+ G = nx.path_graph(3, create_using=graph_type)
+ vals = dict(zip(sorted(G.nodes()), range(len(G))))
+ attr = "hi"
+ nx.set_node_attributes(G, vals, attr)
+ assert G.nodes[0][attr] == 0
+ assert G.nodes[1][attr] == 1
+ assert G.nodes[2][attr] == 2
+
+ # Test dictionary of dictionaries
+ G = nx.path_graph(3, create_using=graph_type)
+ d = {"hi": 0, "hello": 200}
+ vals = dict.fromkeys(G.nodes(), d)
+ vals.pop(0)
+ nx.set_node_attributes(G, vals)
+ assert G.nodes[0] == {}
+ assert G.nodes[1]["hi"] == 0
+ assert G.nodes[2]["hello"] == 200
+
+
+@pytest.mark.parametrize(
+ ("values", "name"),
+ (
+ ({0: "red", 1: "blue"}, "color"), # values dictionary
+ ({0: {"color": "red"}, 1: {"color": "blue"}}, None), # dict-of-dict
+ ),
+)
+def test_set_node_attributes_ignores_extra_nodes(values, name):
+ """
+ When `values` is a dict or dict-of-dict keyed by nodes, ensure that keys
+ that correspond to nodes not in G are ignored.
+ """
+ G = nx.Graph()
+ G.add_node(0)
+ nx.set_node_attributes(G, values, name)
+ assert G.nodes[0]["color"] == "red"
+ assert 1 not in G.nodes
+
+
+@pytest.mark.parametrize("graph_type", (nx.Graph, nx.DiGraph))
+def test_set_edge_attributes(graph_type):
+ # Test single value
+ G = nx.path_graph(3, create_using=graph_type)
+ attr = "hello"
+ vals = 3
+ nx.set_edge_attributes(G, vals, attr)
+ assert G[0][1][attr] == vals
+ assert G[1][2][attr] == vals
+
+ # Test multiple values
+ G = nx.path_graph(3, create_using=graph_type)
+ attr = "hi"
+ edges = [(0, 1), (1, 2)]
+ vals = dict(zip(edges, range(len(edges))))
+ nx.set_edge_attributes(G, vals, attr)
+ assert G[0][1][attr] == 0
+ assert G[1][2][attr] == 1
+
+ # Test dictionary of dictionaries
+ G = nx.path_graph(3, create_using=graph_type)
+ d = {"hi": 0, "hello": 200}
+ edges = [(0, 1)]
+ vals = dict.fromkeys(edges, d)
+ nx.set_edge_attributes(G, vals)
+ assert G[0][1]["hi"] == 0
+ assert G[0][1]["hello"] == 200
+ assert G[1][2] == {}
+
+
+@pytest.mark.parametrize(
+ ("values", "name"),
+ (
+ ({(0, 1): 1.0, (0, 2): 2.0}, "weight"), # values dict
+ ({(0, 1): {"weight": 1.0}, (0, 2): {"weight": 2.0}}, None), # values dod
+ ),
+)
+def test_set_edge_attributes_ignores_extra_edges(values, name):
+ """If `values` is a dict or dict-of-dicts containing edges that are not in
+ G, data associate with these edges should be ignored.
+ """
+ G = nx.Graph([(0, 1)])
+ nx.set_edge_attributes(G, values, name)
+ assert G[0][1]["weight"] == 1.0
+ assert (0, 2) not in G.edges
+
+
+@pytest.mark.parametrize("graph_type", (nx.MultiGraph, nx.MultiDiGraph))
+def test_set_edge_attributes_multi(graph_type):
+ # Test single value
+ G = nx.path_graph(3, create_using=graph_type)
+ attr = "hello"
+ vals = 3
+ nx.set_edge_attributes(G, vals, attr)
+ assert G[0][1][0][attr] == vals
+ assert G[1][2][0][attr] == vals
+
+ # Test multiple values
+ G = nx.path_graph(3, create_using=graph_type)
+ attr = "hi"
+ edges = [(0, 1, 0), (1, 2, 0)]
+ vals = dict(zip(edges, range(len(edges))))
+ nx.set_edge_attributes(G, vals, attr)
+ assert G[0][1][0][attr] == 0
+ assert G[1][2][0][attr] == 1
+
+ # Test dictionary of dictionaries
+ G = nx.path_graph(3, create_using=graph_type)
+ d = {"hi": 0, "hello": 200}
+ edges = [(0, 1, 0)]
+ vals = dict.fromkeys(edges, d)
+ nx.set_edge_attributes(G, vals)
+ assert G[0][1][0]["hi"] == 0
+ assert G[0][1][0]["hello"] == 200
+ assert G[1][2][0] == {}
+
+
+@pytest.mark.parametrize(
+ ("values", "name"),
+ (
+ ({(0, 1, 0): 1.0, (0, 2, 0): 2.0}, "weight"), # values dict
+ ({(0, 1, 0): {"weight": 1.0}, (0, 2, 0): {"weight": 2.0}}, None), # values dod
+ ),
+)
+def test_set_edge_attributes_multi_ignores_extra_edges(values, name):
+ """If `values` is a dict or dict-of-dicts containing edges that are not in
+ G, data associate with these edges should be ignored.
+ """
+ G = nx.MultiGraph([(0, 1, 0), (0, 1, 1)])
+ nx.set_edge_attributes(G, values, name)
+ assert G[0][1][0]["weight"] == 1.0
+ assert G[0][1][1] == {}
+ assert (0, 2) not in G.edges()
+
+
+def test_get_node_attributes():
+ graphs = [nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()]
+ for G in graphs:
+ G = nx.path_graph(3, create_using=G)
+ attr = "hello"
+ vals = 100
+ nx.set_node_attributes(G, vals, attr)
+ attrs = nx.get_node_attributes(G, attr)
+ assert attrs[0] == vals
+ assert attrs[1] == vals
+ assert attrs[2] == vals
+ default_val = 1
+ G.add_node(4)
+ attrs = nx.get_node_attributes(G, attr, default=default_val)
+ assert attrs[4] == default_val
+
+
+def test_get_edge_attributes():
+ graphs = [nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()]
+ for G in graphs:
+ G = nx.path_graph(3, create_using=G)
+ attr = "hello"
+ vals = 100
+ nx.set_edge_attributes(G, vals, attr)
+ attrs = nx.get_edge_attributes(G, attr)
+ assert len(attrs) == 2
+
+ for edge in G.edges:
+ assert attrs[edge] == vals
+
+ default_val = vals
+ G.add_edge(4, 5)
+ deafult_attrs = nx.get_edge_attributes(G, attr, default=default_val)
+ assert len(deafult_attrs) == 3
+
+ for edge in G.edges:
+ assert deafult_attrs[edge] == vals
+
+
+@pytest.mark.parametrize(
+ "graph_type", (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
+)
+def test_remove_node_attributes(graph_type):
+ # Test removing single attribute
+ G = nx.path_graph(3, create_using=graph_type)
+ vals = 100
+ attr = "hello"
+ nx.set_node_attributes(G, vals, attr)
+ nx.remove_node_attributes(G, attr)
+ assert attr not in G.nodes[0]
+ assert attr not in G.nodes[1]
+ assert attr not in G.nodes[2]
+
+ # Test removing single attribute when multiple present
+ G = nx.path_graph(3, create_using=graph_type)
+ other_vals = 200
+ other_attr = "other"
+ nx.set_node_attributes(G, vals, attr)
+ nx.set_node_attributes(G, other_vals, other_attr)
+ nx.remove_node_attributes(G, attr)
+ assert attr not in G.nodes[0]
+ assert G.nodes[0][other_attr] == other_vals
+ assert attr not in G.nodes[1]
+ assert G.nodes[1][other_attr] == other_vals
+ assert attr not in G.nodes[2]
+ assert G.nodes[2][other_attr] == other_vals
+
+ # Test removing multiple attributes
+ G = nx.path_graph(3, create_using=graph_type)
+ nx.set_node_attributes(G, vals, attr)
+ nx.set_node_attributes(G, other_vals, other_attr)
+ nx.remove_node_attributes(G, attr, other_attr)
+ assert attr not in G.nodes[0] and other_attr not in G.nodes[0]
+ assert attr not in G.nodes[1] and other_attr not in G.nodes[1]
+ assert attr not in G.nodes[2] and other_attr not in G.nodes[2]
+
+ # Test removing multiple (but not all) attributes
+ G = nx.path_graph(3, create_using=graph_type)
+ third_vals = 300
+ third_attr = "three"
+ nx.set_node_attributes(
+ G,
+ {
+ n: {attr: vals, other_attr: other_vals, third_attr: third_vals}
+ for n in G.nodes()
+ },
+ )
+ nx.remove_node_attributes(G, other_attr, third_attr)
+ assert other_attr not in G.nodes[0] and third_attr not in G.nodes[0]
+ assert other_attr not in G.nodes[1] and third_attr not in G.nodes[1]
+ assert other_attr not in G.nodes[2] and third_attr not in G.nodes[2]
+ assert G.nodes[0][attr] == vals
+ assert G.nodes[1][attr] == vals
+ assert G.nodes[2][attr] == vals
+
+ # Test incomplete node attributes
+ G = nx.path_graph(3, create_using=graph_type)
+ nx.set_node_attributes(
+ G,
+ {
+ 1: {attr: vals, other_attr: other_vals},
+ 2: {attr: vals, other_attr: other_vals},
+ },
+ )
+ nx.remove_node_attributes(G, attr)
+ assert attr not in G.nodes[0]
+ assert attr not in G.nodes[1]
+ assert attr not in G.nodes[2]
+ assert G.nodes[1][other_attr] == other_vals
+ assert G.nodes[2][other_attr] == other_vals
+
+ # Test removing on a subset of nodes
+ G = nx.path_graph(3, create_using=graph_type)
+ nx.set_node_attributes(
+ G,
+ {
+ n: {attr: vals, other_attr: other_vals, third_attr: third_vals}
+ for n in G.nodes()
+ },
+ )
+ nx.remove_node_attributes(G, attr, other_attr, nbunch=[0, 1])
+ assert attr not in G.nodes[0] and other_attr not in G.nodes[0]
+ assert attr not in G.nodes[1] and other_attr not in G.nodes[1]
+ assert attr in G.nodes[2] and other_attr in G.nodes[2]
+ assert third_attr in G.nodes[0] and G.nodes[0][third_attr] == third_vals
+ assert third_attr in G.nodes[1] and G.nodes[1][third_attr] == third_vals
+
+
+@pytest.mark.parametrize("graph_type", (nx.Graph, nx.DiGraph))
+def test_remove_edge_attributes(graph_type):
+ # Test removing single attribute
+ G = nx.path_graph(3, create_using=graph_type)
+ attr = "hello"
+ vals = 100
+ nx.set_edge_attributes(G, vals, attr)
+ nx.remove_edge_attributes(G, attr)
+ assert len(nx.get_edge_attributes(G, attr)) == 0
+
+ # Test removing only some attributes
+ G = nx.path_graph(3, create_using=graph_type)
+ other_attr = "other"
+ other_vals = 200
+ nx.set_edge_attributes(G, vals, attr)
+ nx.set_edge_attributes(G, other_vals, other_attr)
+ nx.remove_edge_attributes(G, attr)
+
+ assert attr not in G[0][1]
+ assert attr not in G[1][2]
+ assert G[0][1][other_attr] == 200
+ assert G[1][2][other_attr] == 200
+
+ # Test removing multiple attributes
+ G = nx.path_graph(3, create_using=graph_type)
+ nx.set_edge_attributes(G, vals, attr)
+ nx.set_edge_attributes(G, other_vals, other_attr)
+ nx.remove_edge_attributes(G, attr, other_attr)
+ assert attr not in G[0][1] and other_attr not in G[0][1]
+ assert attr not in G[1][2] and other_attr not in G[1][2]
+
+ # Test removing multiple (not all) attributes
+ G = nx.path_graph(3, create_using=graph_type)
+ third_attr = "third"
+ third_vals = 300
+ nx.set_edge_attributes(
+ G,
+ {
+ (u, v): {attr: vals, other_attr: other_vals, third_attr: third_vals}
+ for u, v in G.edges()
+ },
+ )
+ nx.remove_edge_attributes(G, other_attr, third_attr)
+ assert other_attr not in G[0][1] and third_attr not in G[0][1]
+ assert other_attr not in G[1][2] and third_attr not in G[1][2]
+ assert G[0][1][attr] == vals
+ assert G[1][2][attr] == vals
+
+ # Test removing incomplete edge attributes
+ G = nx.path_graph(3, create_using=graph_type)
+ nx.set_edge_attributes(G, {(0, 1): {attr: vals, other_attr: other_vals}})
+ nx.remove_edge_attributes(G, other_attr)
+ assert other_attr not in G[0][1] and G[0][1][attr] == vals
+ assert other_attr not in G[1][2]
+
+ # Test removing subset of edge attributes
+ G = nx.path_graph(3, create_using=graph_type)
+ nx.set_edge_attributes(
+ G,
+ {
+ (u, v): {attr: vals, other_attr: other_vals, third_attr: third_vals}
+ for u, v in G.edges()
+ },
+ )
+ nx.remove_edge_attributes(G, other_attr, third_attr, ebunch=[(0, 1)])
+ assert other_attr not in G[0][1] and third_attr not in G[0][1]
+ assert other_attr in G[1][2] and third_attr in G[1][2]
+
+
+@pytest.mark.parametrize("graph_type", (nx.MultiGraph, nx.MultiDiGraph))
+def test_remove_multi_edge_attributes(graph_type):
+ # Test removing single attribute
+ G = nx.path_graph(3, create_using=graph_type)
+ G.add_edge(1, 2)
+ attr = "hello"
+ vals = 100
+ nx.set_edge_attributes(G, vals, attr)
+ nx.remove_edge_attributes(G, attr)
+ assert attr not in G[0][1][0]
+ assert attr not in G[1][2][0]
+ assert attr not in G[1][2][1]
+
+ # Test removing only some attributes
+ G = nx.path_graph(3, create_using=graph_type)
+ G.add_edge(1, 2)
+ other_attr = "other"
+ other_vals = 200
+ nx.set_edge_attributes(G, vals, attr)
+ nx.set_edge_attributes(G, other_vals, other_attr)
+ nx.remove_edge_attributes(G, attr)
+ assert attr not in G[0][1][0]
+ assert attr not in G[1][2][0]
+ assert attr not in G[1][2][1]
+ assert G[0][1][0][other_attr] == other_vals
+ assert G[1][2][0][other_attr] == other_vals
+ assert G[1][2][1][other_attr] == other_vals
+
+ # Test removing multiple attributes
+ G = nx.path_graph(3, create_using=graph_type)
+ G.add_edge(1, 2)
+ nx.set_edge_attributes(G, vals, attr)
+ nx.set_edge_attributes(G, other_vals, other_attr)
+ nx.remove_edge_attributes(G, attr, other_attr)
+ assert attr not in G[0][1][0] and other_attr not in G[0][1][0]
+ assert attr not in G[1][2][0] and other_attr not in G[1][2][0]
+ assert attr not in G[1][2][1] and other_attr not in G[1][2][1]
+
+ # Test removing multiple (not all) attributes
+ G = nx.path_graph(3, create_using=graph_type)
+ G.add_edge(1, 2)
+ third_attr = "third"
+ third_vals = 300
+ nx.set_edge_attributes(
+ G,
+ {
+ (u, v, k): {attr: vals, other_attr: other_vals, third_attr: third_vals}
+ for u, v, k in G.edges(keys=True)
+ },
+ )
+ nx.remove_edge_attributes(G, other_attr, third_attr)
+ assert other_attr not in G[0][1][0] and third_attr not in G[0][1][0]
+ assert other_attr not in G[1][2][0] and other_attr not in G[1][2][0]
+ assert other_attr not in G[1][2][1] and other_attr not in G[1][2][1]
+ assert G[0][1][0][attr] == vals
+ assert G[1][2][0][attr] == vals
+ assert G[1][2][1][attr] == vals
+
+ # Test removing incomplete edge attributes
+ G = nx.path_graph(3, create_using=graph_type)
+ G.add_edge(1, 2)
+ nx.set_edge_attributes(
+ G,
+ {
+ (0, 1, 0): {attr: vals, other_attr: other_vals},
+ (1, 2, 1): {attr: vals, other_attr: other_vals},
+ },
+ )
+ nx.remove_edge_attributes(G, other_attr)
+ assert other_attr not in G[0][1][0] and G[0][1][0][attr] == vals
+ assert other_attr not in G[1][2][0]
+ assert other_attr not in G[1][2][1]
+
+ # Test removing subset of edge attributes
+ G = nx.path_graph(3, create_using=graph_type)
+ G.add_edge(1, 2)
+ nx.set_edge_attributes(
+ G,
+ {
+ (0, 1, 0): {attr: vals, other_attr: other_vals},
+ (1, 2, 0): {attr: vals, other_attr: other_vals},
+ (1, 2, 1): {attr: vals, other_attr: other_vals},
+ },
+ )
+ nx.remove_edge_attributes(G, attr, ebunch=[(0, 1, 0), (1, 2, 0)])
+ assert attr not in G[0][1][0] and other_attr in G[0][1][0]
+ assert attr not in G[1][2][0] and other_attr in G[1][2][0]
+ assert attr in G[1][2][1] and other_attr in G[1][2][1]
+
+
+def test_is_empty():
+ graphs = [nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()]
+ for G in graphs:
+ assert nx.is_empty(G)
+ G.add_nodes_from(range(5))
+ assert nx.is_empty(G)
+ G.add_edges_from([(1, 2), (3, 4)])
+ assert not nx.is_empty(G)
+
+
+@pytest.mark.parametrize(
+ "graph_type", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
+)
+def test_selfloops(graph_type):
+ G = nx.complete_graph(3, create_using=graph_type)
+ G.add_edge(0, 0)
+ assert nodes_equal(nx.nodes_with_selfloops(G), [0])
+ assert edges_equal(nx.selfloop_edges(G), [(0, 0)])
+ assert edges_equal(nx.selfloop_edges(G, data=True), [(0, 0, {})])
+ assert nx.number_of_selfloops(G) == 1
+
+
+@pytest.mark.parametrize(
+ "graph_type", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
+)
+def test_selfloop_edges_attr(graph_type):
+ G = nx.complete_graph(3, create_using=graph_type)
+ G.add_edge(0, 0)
+ G.add_edge(1, 1, weight=2)
+ assert edges_equal(
+ nx.selfloop_edges(G, data=True), [(0, 0, {}), (1, 1, {"weight": 2})]
+ )
+ assert edges_equal(nx.selfloop_edges(G, data="weight"), [(0, 0, None), (1, 1, 2)])
+
+
+def test_selfloop_edges_multi_with_data_and_keys():
+ G = nx.complete_graph(3, create_using=nx.MultiGraph)
+ G.add_edge(0, 0, weight=10)
+ G.add_edge(0, 0, weight=100)
+ assert edges_equal(
+ nx.selfloop_edges(G, data="weight", keys=True), [(0, 0, 0, 10), (0, 0, 1, 100)]
+ )
+
+
+@pytest.mark.parametrize("graph_type", [nx.Graph, nx.DiGraph])
+def test_selfloops_removal(graph_type):
+ G = nx.complete_graph(3, create_using=graph_type)
+ G.add_edge(0, 0)
+ G.remove_edges_from(nx.selfloop_edges(G, keys=True))
+ G.add_edge(0, 0)
+ G.remove_edges_from(nx.selfloop_edges(G, data=True))
+ G.add_edge(0, 0)
+ G.remove_edges_from(nx.selfloop_edges(G, keys=True, data=True))
+
+
+@pytest.mark.parametrize("graph_type", [nx.MultiGraph, nx.MultiDiGraph])
+def test_selfloops_removal_multi(graph_type):
+ """test removing selfloops behavior vis-a-vis altering a dict while iterating.
+ cf. gh-4068"""
+ G = nx.complete_graph(3, create_using=graph_type)
+ # Defaults - see gh-4080
+ G.add_edge(0, 0)
+ G.add_edge(0, 0)
+ G.remove_edges_from(nx.selfloop_edges(G))
+ assert (0, 0) not in G.edges()
+ # With keys
+ G.add_edge(0, 0)
+ G.add_edge(0, 0)
+ with pytest.raises(RuntimeError):
+ G.remove_edges_from(nx.selfloop_edges(G, keys=True))
+ # With data
+ G.add_edge(0, 0)
+ G.add_edge(0, 0)
+ with pytest.raises(TypeError):
+ G.remove_edges_from(nx.selfloop_edges(G, data=True))
+ # With keys and data
+ G.add_edge(0, 0)
+ G.add_edge(0, 0)
+ with pytest.raises(RuntimeError):
+ G.remove_edges_from(nx.selfloop_edges(G, data=True, keys=True))
+
+
+def test_pathweight():
+ valid_path = [1, 2, 3]
+ invalid_path = [1, 3, 2]
+ graphs = [nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()]
+ edges = [
+ (1, 2, {"cost": 5, "dist": 6}),
+ (2, 3, {"cost": 3, "dist": 4}),
+ (1, 2, {"cost": 1, "dist": 2}),
+ ]
+ for graph in graphs:
+ graph.add_edges_from(edges)
+ assert nx.path_weight(graph, valid_path, "cost") == 4
+ assert nx.path_weight(graph, valid_path, "dist") == 6
+ pytest.raises(nx.NetworkXNoPath, nx.path_weight, graph, invalid_path, "cost")
+
+
+@pytest.mark.parametrize(
+ "G", (nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph())
+)
+def test_ispath(G):
+ G.add_edges_from([(1, 2), (2, 3), (1, 2), (3, 4)])
+ valid_path = [1, 2, 3, 4]
+ invalid_path = [1, 2, 4, 3] # wrong node order
+ another_invalid_path = [1, 2, 3, 4, 5] # contains node not in G
+ assert nx.is_path(G, valid_path)
+ assert not nx.is_path(G, invalid_path)
+ assert not nx.is_path(G, another_invalid_path)
+
+
+@pytest.mark.parametrize("G", (nx.Graph(), nx.DiGraph()))
+def test_restricted_view(G):
+ G.add_edges_from([(0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2)])
+ G.add_node(4)
+ H = nx.restricted_view(G, [0, 2, 5], [(1, 2), (3, 4)])
+ assert set(H.nodes()) == {1, 3, 4}
+ assert set(H.edges()) == {(1, 1)}
+
+
+@pytest.mark.parametrize("G", (nx.MultiGraph(), nx.MultiDiGraph()))
+def test_restricted_view_multi(G):
+ G.add_edges_from(
+ [(0, 1, 0), (0, 2, 0), (0, 3, 0), (0, 1, 1), (1, 0, 0), (1, 1, 0), (1, 2, 0)]
+ )
+ G.add_node(4)
+ H = nx.restricted_view(G, [0, 2, 5], [(1, 2, 0), (3, 4, 0)])
+ assert set(H.nodes()) == {1, 3, 4}
+ assert set(H.edges()) == {(1, 1)}
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_graph.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_graph.py
new file mode 100644
index 00000000..b0048a31
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_graph.py
@@ -0,0 +1,920 @@
+import gc
+import pickle
+import platform
+import weakref
+
+import pytest
+
+import networkx as nx
+from networkx.utils import edges_equal, graphs_equal, nodes_equal
+
+
+class BaseGraphTester:
+ """Tests for data-structure independent graph class features."""
+
+ def test_contains(self):
+ G = self.K3
+ assert 1 in G
+ assert 4 not in G
+ assert "b" not in G
+ assert [] not in G # no exception for nonhashable
+ assert {1: 1} not in G # no exception for nonhashable
+
+ def test_order(self):
+ G = self.K3
+ assert len(G) == 3
+ assert G.order() == 3
+ assert G.number_of_nodes() == 3
+
+ def test_nodes(self):
+ G = self.K3
+ assert isinstance(G._node, G.node_dict_factory)
+ assert isinstance(G._adj, G.adjlist_outer_dict_factory)
+ assert all(
+ isinstance(adj, G.adjlist_inner_dict_factory) for adj in G._adj.values()
+ )
+ assert sorted(G.nodes()) == self.k3nodes
+ assert sorted(G.nodes(data=True)) == [(0, {}), (1, {}), (2, {})]
+
+ def test_none_node(self):
+ G = self.Graph()
+ with pytest.raises(ValueError):
+ G.add_node(None)
+ with pytest.raises(ValueError):
+ G.add_nodes_from([None])
+ with pytest.raises(ValueError):
+ G.add_edge(0, None)
+ with pytest.raises(ValueError):
+ G.add_edges_from([(0, None)])
+
+ def test_has_node(self):
+ G = self.K3
+ assert G.has_node(1)
+ assert not G.has_node(4)
+ assert not G.has_node([]) # no exception for nonhashable
+ assert not G.has_node({1: 1}) # no exception for nonhashable
+
+ def test_has_edge(self):
+ G = self.K3
+ assert G.has_edge(0, 1)
+ assert not G.has_edge(0, -1)
+
+ def test_neighbors(self):
+ G = self.K3
+ assert sorted(G.neighbors(0)) == [1, 2]
+ with pytest.raises(nx.NetworkXError):
+ G.neighbors(-1)
+
+ @pytest.mark.skipif(
+ platform.python_implementation() == "PyPy", reason="PyPy gc is different"
+ )
+ def test_memory_leak(self):
+ G = self.Graph()
+
+ def count_objects_of_type(_type):
+ # Iterating over all objects tracked by gc can include weak references
+ # whose weakly-referenced objects may no longer exist. Calling `isinstance`
+ # on such a weak reference will raise ReferenceError. There are at least
+ # three workarounds for this: one is to compare type names instead of using
+ # `isinstance` such as `type(obj).__name__ == typename`, another is to use
+ # `type(obj) == _type`, and the last is to ignore ProxyTypes as we do below.
+ # NOTE: even if this safeguard is deemed unnecessary to pass NetworkX tests,
+ # we should still keep it for maximum safety for other NetworkX backends.
+ return sum(
+ 1
+ for obj in gc.get_objects()
+ if not isinstance(obj, weakref.ProxyTypes) and isinstance(obj, _type)
+ )
+
+ gc.collect()
+ before = count_objects_of_type(self.Graph)
+ G.copy()
+ gc.collect()
+ after = count_objects_of_type(self.Graph)
+ assert before == after
+
+ # test a subgraph of the base class
+ class MyGraph(self.Graph):
+ pass
+
+ gc.collect()
+ G = MyGraph()
+ before = count_objects_of_type(MyGraph)
+ G.copy()
+ gc.collect()
+ after = count_objects_of_type(MyGraph)
+ assert before == after
+
+ def test_edges(self):
+ G = self.K3
+ assert isinstance(G._adj, G.adjlist_outer_dict_factory)
+ assert edges_equal(G.edges(), [(0, 1), (0, 2), (1, 2)])
+ assert edges_equal(G.edges(0), [(0, 1), (0, 2)])
+ assert edges_equal(G.edges([0, 1]), [(0, 1), (0, 2), (1, 2)])
+ with pytest.raises(nx.NetworkXError):
+ G.edges(-1)
+
+ def test_degree(self):
+ G = self.K3
+ assert sorted(G.degree()) == [(0, 2), (1, 2), (2, 2)]
+ assert dict(G.degree()) == {0: 2, 1: 2, 2: 2}
+ assert G.degree(0) == 2
+ with pytest.raises(nx.NetworkXError):
+ G.degree(-1) # node not in graph
+
+ def test_size(self):
+ G = self.K3
+ assert G.size() == 3
+ assert G.number_of_edges() == 3
+
+ def test_nbunch_iter(self):
+ G = self.K3
+ assert nodes_equal(G.nbunch_iter(), self.k3nodes) # all nodes
+ assert nodes_equal(G.nbunch_iter(0), [0]) # single node
+ assert nodes_equal(G.nbunch_iter([0, 1]), [0, 1]) # sequence
+ # sequence with none in graph
+ assert nodes_equal(G.nbunch_iter([-1]), [])
+ # string sequence with none in graph
+ assert nodes_equal(G.nbunch_iter("foo"), [])
+ # node not in graph doesn't get caught upon creation of iterator
+ bunch = G.nbunch_iter(-1)
+ # but gets caught when iterator used
+ with pytest.raises(nx.NetworkXError, match="is not a node or a sequence"):
+ list(bunch)
+ # unhashable doesn't get caught upon creation of iterator
+ bunch = G.nbunch_iter([0, 1, 2, {}])
+ # but gets caught when iterator hits the unhashable
+ with pytest.raises(
+ nx.NetworkXError, match="in sequence nbunch is not a valid node"
+ ):
+ list(bunch)
+
+ def test_nbunch_iter_node_format_raise(self):
+ # Tests that a node that would have failed string formatting
+ # doesn't cause an error when attempting to raise a
+ # :exc:`nx.NetworkXError`.
+
+ # For more information, see pull request #1813.
+ G = self.Graph()
+ nbunch = [("x", set())]
+ with pytest.raises(nx.NetworkXError):
+ list(G.nbunch_iter(nbunch))
+
+ def test_selfloop_degree(self):
+ G = self.Graph()
+ G.add_edge(1, 1)
+ assert sorted(G.degree()) == [(1, 2)]
+ assert dict(G.degree()) == {1: 2}
+ assert G.degree(1) == 2
+ assert sorted(G.degree([1])) == [(1, 2)]
+ assert G.degree(1, weight="weight") == 2
+
+ def test_selfloops(self):
+ G = self.K3.copy()
+ G.add_edge(0, 0)
+ assert nodes_equal(nx.nodes_with_selfloops(G), [0])
+ assert edges_equal(nx.selfloop_edges(G), [(0, 0)])
+ assert nx.number_of_selfloops(G) == 1
+ G.remove_edge(0, 0)
+ G.add_edge(0, 0)
+ G.remove_edges_from([(0, 0)])
+ G.add_edge(1, 1)
+ G.remove_node(1)
+ G.add_edge(0, 0)
+ G.add_edge(1, 1)
+ G.remove_nodes_from([0, 1])
+
+ def test_cache_reset(self):
+ G = self.K3.copy()
+ old_adj = G.adj
+ assert id(G.adj) == id(old_adj)
+ G._adj = {}
+ assert id(G.adj) != id(old_adj)
+
+ old_nodes = G.nodes
+ assert id(G.nodes) == id(old_nodes)
+ G._node = {}
+ assert id(G.nodes) != id(old_nodes)
+
+ def test_attributes_cached(self):
+ G = self.K3.copy()
+ assert id(G.nodes) == id(G.nodes)
+ assert id(G.edges) == id(G.edges)
+ assert id(G.degree) == id(G.degree)
+ assert id(G.adj) == id(G.adj)
+
+
+class BaseAttrGraphTester(BaseGraphTester):
+ """Tests of graph class attribute features."""
+
+ def test_weighted_degree(self):
+ G = self.Graph()
+ G.add_edge(1, 2, weight=2, other=3)
+ G.add_edge(2, 3, weight=3, other=4)
+ assert sorted(d for n, d in G.degree(weight="weight")) == [2, 3, 5]
+ assert dict(G.degree(weight="weight")) == {1: 2, 2: 5, 3: 3}
+ assert G.degree(1, weight="weight") == 2
+ assert nodes_equal((G.degree([1], weight="weight")), [(1, 2)])
+
+ assert nodes_equal((d for n, d in G.degree(weight="other")), [3, 7, 4])
+ assert dict(G.degree(weight="other")) == {1: 3, 2: 7, 3: 4}
+ assert G.degree(1, weight="other") == 3
+ assert edges_equal((G.degree([1], weight="other")), [(1, 3)])
+
+ def add_attributes(self, G):
+ G.graph["foo"] = []
+ G.nodes[0]["foo"] = []
+ G.remove_edge(1, 2)
+ ll = []
+ G.add_edge(1, 2, foo=ll)
+ G.add_edge(2, 1, foo=ll)
+
+ def test_name(self):
+ G = self.Graph(name="")
+ assert G.name == ""
+ G = self.Graph(name="test")
+ assert G.name == "test"
+
+ def test_str_unnamed(self):
+ G = self.Graph()
+ G.add_edges_from([(1, 2), (2, 3)])
+ assert str(G) == f"{type(G).__name__} with 3 nodes and 2 edges"
+
+ def test_str_named(self):
+ G = self.Graph(name="foo")
+ G.add_edges_from([(1, 2), (2, 3)])
+ assert str(G) == f"{type(G).__name__} named 'foo' with 3 nodes and 2 edges"
+
+ def test_graph_chain(self):
+ G = self.Graph([(0, 1), (1, 2)])
+ DG = G.to_directed(as_view=True)
+ SDG = DG.subgraph([0, 1])
+ RSDG = SDG.reverse(copy=False)
+ assert G is DG._graph
+ assert DG is SDG._graph
+ assert SDG is RSDG._graph
+
+ def test_copy(self):
+ G = self.Graph()
+ G.add_node(0)
+ G.add_edge(1, 2)
+ self.add_attributes(G)
+ # copy edge datadict but any container attr are same
+ H = G.copy()
+ self.graphs_equal(H, G)
+ self.different_attrdict(H, G)
+ self.shallow_copy_attrdict(H, G)
+
+ def test_class_copy(self):
+ G = self.Graph()
+ G.add_node(0)
+ G.add_edge(1, 2)
+ self.add_attributes(G)
+ # copy edge datadict but any container attr are same
+ H = G.__class__(G)
+ self.graphs_equal(H, G)
+ self.different_attrdict(H, G)
+ self.shallow_copy_attrdict(H, G)
+
+ def test_fresh_copy(self):
+ G = self.Graph()
+ G.add_node(0)
+ G.add_edge(1, 2)
+ self.add_attributes(G)
+ # copy graph structure but use fresh datadict
+ H = G.__class__()
+ H.add_nodes_from(G)
+ H.add_edges_from(G.edges())
+ assert len(G.nodes[0]) == 1
+ ddict = G.adj[1][2][0] if G.is_multigraph() else G.adj[1][2]
+ assert len(ddict) == 1
+ assert len(H.nodes[0]) == 0
+ ddict = H.adj[1][2][0] if H.is_multigraph() else H.adj[1][2]
+ assert len(ddict) == 0
+
+ def is_deepcopy(self, H, G):
+ self.graphs_equal(H, G)
+ self.different_attrdict(H, G)
+ self.deep_copy_attrdict(H, G)
+
+ def deep_copy_attrdict(self, H, G):
+ self.deepcopy_graph_attr(H, G)
+ self.deepcopy_node_attr(H, G)
+ self.deepcopy_edge_attr(H, G)
+
+ def deepcopy_graph_attr(self, H, G):
+ assert G.graph["foo"] == H.graph["foo"]
+ G.graph["foo"].append(1)
+ assert G.graph["foo"] != H.graph["foo"]
+
+ def deepcopy_node_attr(self, H, G):
+ assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
+ G.nodes[0]["foo"].append(1)
+ assert G.nodes[0]["foo"] != H.nodes[0]["foo"]
+
+ def deepcopy_edge_attr(self, H, G):
+ assert G[1][2]["foo"] == H[1][2]["foo"]
+ G[1][2]["foo"].append(1)
+ assert G[1][2]["foo"] != H[1][2]["foo"]
+
+ def is_shallow_copy(self, H, G):
+ self.graphs_equal(H, G)
+ self.shallow_copy_attrdict(H, G)
+
+ def shallow_copy_attrdict(self, H, G):
+ self.shallow_copy_graph_attr(H, G)
+ self.shallow_copy_node_attr(H, G)
+ self.shallow_copy_edge_attr(H, G)
+
+ def shallow_copy_graph_attr(self, H, G):
+ assert G.graph["foo"] == H.graph["foo"]
+ G.graph["foo"].append(1)
+ assert G.graph["foo"] == H.graph["foo"]
+
+ def shallow_copy_node_attr(self, H, G):
+ assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
+ G.nodes[0]["foo"].append(1)
+ assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
+
+ def shallow_copy_edge_attr(self, H, G):
+ assert G[1][2]["foo"] == H[1][2]["foo"]
+ G[1][2]["foo"].append(1)
+ assert G[1][2]["foo"] == H[1][2]["foo"]
+
+ def same_attrdict(self, H, G):
+ old_foo = H[1][2]["foo"]
+ H.adj[1][2]["foo"] = "baz"
+ assert G.edges == H.edges
+ H.adj[1][2]["foo"] = old_foo
+ assert G.edges == H.edges
+
+ old_foo = H.nodes[0]["foo"]
+ H.nodes[0]["foo"] = "baz"
+ assert G.nodes == H.nodes
+ H.nodes[0]["foo"] = old_foo
+ assert G.nodes == H.nodes
+
+ def different_attrdict(self, H, G):
+ old_foo = H[1][2]["foo"]
+ H.adj[1][2]["foo"] = "baz"
+ assert G._adj != H._adj
+ H.adj[1][2]["foo"] = old_foo
+ assert G._adj == H._adj
+
+ old_foo = H.nodes[0]["foo"]
+ H.nodes[0]["foo"] = "baz"
+ assert G._node != H._node
+ H.nodes[0]["foo"] = old_foo
+ assert G._node == H._node
+
+ def graphs_equal(self, H, G):
+ assert G._adj == H._adj
+ assert G._node == H._node
+ assert G.graph == H.graph
+ assert G.name == H.name
+ if not G.is_directed() and not H.is_directed():
+ assert H._adj[1][2] is H._adj[2][1]
+ assert G._adj[1][2] is G._adj[2][1]
+ else: # at least one is directed
+ if not G.is_directed():
+ G._pred = G._adj
+ G._succ = G._adj
+ if not H.is_directed():
+ H._pred = H._adj
+ H._succ = H._adj
+ assert G._pred == H._pred
+ assert G._succ == H._succ
+ assert H._succ[1][2] is H._pred[2][1]
+ assert G._succ[1][2] is G._pred[2][1]
+
+ def test_graph_attr(self):
+ G = self.K3.copy()
+ G.graph["foo"] = "bar"
+ assert isinstance(G.graph, G.graph_attr_dict_factory)
+ assert G.graph["foo"] == "bar"
+ del G.graph["foo"]
+ assert G.graph == {}
+ H = self.Graph(foo="bar")
+ assert H.graph["foo"] == "bar"
+
+ def test_node_attr(self):
+ G = self.K3.copy()
+ G.add_node(1, foo="bar")
+ assert all(
+ isinstance(d, G.node_attr_dict_factory) for u, d in G.nodes(data=True)
+ )
+ assert nodes_equal(G.nodes(), [0, 1, 2])
+ assert nodes_equal(G.nodes(data=True), [(0, {}), (1, {"foo": "bar"}), (2, {})])
+ G.nodes[1]["foo"] = "baz"
+ assert nodes_equal(G.nodes(data=True), [(0, {}), (1, {"foo": "baz"}), (2, {})])
+ assert nodes_equal(G.nodes(data="foo"), [(0, None), (1, "baz"), (2, None)])
+ assert nodes_equal(
+ G.nodes(data="foo", default="bar"), [(0, "bar"), (1, "baz"), (2, "bar")]
+ )
+
+ def test_node_attr2(self):
+ G = self.K3.copy()
+ a = {"foo": "bar"}
+ G.add_node(3, **a)
+ assert nodes_equal(G.nodes(), [0, 1, 2, 3])
+ assert nodes_equal(
+ G.nodes(data=True), [(0, {}), (1, {}), (2, {}), (3, {"foo": "bar"})]
+ )
+
+ def test_edge_lookup(self):
+ G = self.Graph()
+ G.add_edge(1, 2, foo="bar")
+ assert edges_equal(G.edges[1, 2], {"foo": "bar"})
+
+ def test_edge_attr(self):
+ G = self.Graph()
+ G.add_edge(1, 2, foo="bar")
+ assert all(
+ isinstance(d, G.edge_attr_dict_factory) for u, v, d in G.edges(data=True)
+ )
+ assert edges_equal(G.edges(data=True), [(1, 2, {"foo": "bar"})])
+ assert edges_equal(G.edges(data="foo"), [(1, 2, "bar")])
+
+ def test_edge_attr2(self):
+ G = self.Graph()
+ G.add_edges_from([(1, 2), (3, 4)], foo="foo")
+ assert edges_equal(
+ G.edges(data=True), [(1, 2, {"foo": "foo"}), (3, 4, {"foo": "foo"})]
+ )
+ assert edges_equal(G.edges(data="foo"), [(1, 2, "foo"), (3, 4, "foo")])
+
+ def test_edge_attr3(self):
+ G = self.Graph()
+ G.add_edges_from([(1, 2, {"weight": 32}), (3, 4, {"weight": 64})], foo="foo")
+ assert edges_equal(
+ G.edges(data=True),
+ [
+ (1, 2, {"foo": "foo", "weight": 32}),
+ (3, 4, {"foo": "foo", "weight": 64}),
+ ],
+ )
+
+ G.remove_edges_from([(1, 2), (3, 4)])
+ G.add_edge(1, 2, data=7, spam="bar", bar="foo")
+ assert edges_equal(
+ G.edges(data=True), [(1, 2, {"data": 7, "spam": "bar", "bar": "foo"})]
+ )
+
+ def test_edge_attr4(self):
+ G = self.Graph()
+ G.add_edge(1, 2, data=7, spam="bar", bar="foo")
+ assert edges_equal(
+ G.edges(data=True), [(1, 2, {"data": 7, "spam": "bar", "bar": "foo"})]
+ )
+ G[1][2]["data"] = 10 # OK to set data like this
+ assert edges_equal(
+ G.edges(data=True), [(1, 2, {"data": 10, "spam": "bar", "bar": "foo"})]
+ )
+
+ G.adj[1][2]["data"] = 20
+ assert edges_equal(
+ G.edges(data=True), [(1, 2, {"data": 20, "spam": "bar", "bar": "foo"})]
+ )
+ G.edges[1, 2]["data"] = 21 # another spelling, "edge"
+ assert edges_equal(
+ G.edges(data=True), [(1, 2, {"data": 21, "spam": "bar", "bar": "foo"})]
+ )
+ G.adj[1][2]["listdata"] = [20, 200]
+ G.adj[1][2]["weight"] = 20
+ dd = {
+ "data": 21,
+ "spam": "bar",
+ "bar": "foo",
+ "listdata": [20, 200],
+ "weight": 20,
+ }
+ assert edges_equal(G.edges(data=True), [(1, 2, dd)])
+
+ def test_to_undirected(self):
+ G = self.K3
+ self.add_attributes(G)
+ H = nx.Graph(G)
+ self.is_shallow_copy(H, G)
+ self.different_attrdict(H, G)
+ H = G.to_undirected()
+ self.is_deepcopy(H, G)
+
+ def test_to_directed_as_view(self):
+ H = nx.path_graph(2, create_using=self.Graph)
+ H2 = H.to_directed(as_view=True)
+ assert H is H2._graph
+ assert H2.has_edge(0, 1)
+ assert H2.has_edge(1, 0) or H.is_directed()
+ pytest.raises(nx.NetworkXError, H2.add_node, -1)
+ pytest.raises(nx.NetworkXError, H2.add_edge, 1, 2)
+ H.add_edge(1, 2)
+ assert H2.has_edge(1, 2)
+ assert H2.has_edge(2, 1) or H.is_directed()
+
+ def test_to_undirected_as_view(self):
+ H = nx.path_graph(2, create_using=self.Graph)
+ H2 = H.to_undirected(as_view=True)
+ assert H is H2._graph
+ assert H2.has_edge(0, 1)
+ assert H2.has_edge(1, 0)
+ pytest.raises(nx.NetworkXError, H2.add_node, -1)
+ pytest.raises(nx.NetworkXError, H2.add_edge, 1, 2)
+ H.add_edge(1, 2)
+ assert H2.has_edge(1, 2)
+ assert H2.has_edge(2, 1)
+
+ def test_directed_class(self):
+ G = self.Graph()
+
+ class newGraph(G.to_undirected_class()):
+ def to_directed_class(self):
+ return newDiGraph
+
+ def to_undirected_class(self):
+ return newGraph
+
+ class newDiGraph(G.to_directed_class()):
+ def to_directed_class(self):
+ return newDiGraph
+
+ def to_undirected_class(self):
+ return newGraph
+
+ G = newDiGraph() if G.is_directed() else newGraph()
+ H = G.to_directed()
+ assert isinstance(H, newDiGraph)
+ H = G.to_undirected()
+ assert isinstance(H, newGraph)
+
+ def test_to_directed(self):
+ G = self.K3
+ self.add_attributes(G)
+ H = nx.DiGraph(G)
+ self.is_shallow_copy(H, G)
+ self.different_attrdict(H, G)
+ H = G.to_directed()
+ self.is_deepcopy(H, G)
+
+ def test_subgraph(self):
+ G = self.K3
+ self.add_attributes(G)
+ H = G.subgraph([0, 1, 2, 5])
+ self.graphs_equal(H, G)
+ self.same_attrdict(H, G)
+ self.shallow_copy_attrdict(H, G)
+
+ H = G.subgraph(0)
+ assert H.adj == {0: {}}
+ H = G.subgraph([])
+ assert H.adj == {}
+ assert G.adj != {}
+
+ def test_selfloops_attr(self):
+ G = self.K3.copy()
+ G.add_edge(0, 0)
+ G.add_edge(1, 1, weight=2)
+ assert edges_equal(
+ nx.selfloop_edges(G, data=True), [(0, 0, {}), (1, 1, {"weight": 2})]
+ )
+ assert edges_equal(
+ nx.selfloop_edges(G, data="weight"), [(0, 0, None), (1, 1, 2)]
+ )
+
+
+class TestGraph(BaseAttrGraphTester):
+ """Tests specific to dict-of-dict-of-dict graph data structure"""
+
+ def setup_method(self):
+ self.Graph = nx.Graph
+ # build dict-of-dict-of-dict K3
+ ed1, ed2, ed3 = ({}, {}, {})
+ self.k3adj = {0: {1: ed1, 2: ed2}, 1: {0: ed1, 2: ed3}, 2: {0: ed2, 1: ed3}}
+ self.k3edges = [(0, 1), (0, 2), (1, 2)]
+ self.k3nodes = [0, 1, 2]
+ self.K3 = self.Graph()
+ self.K3._adj = self.k3adj
+ self.K3._node = {}
+ self.K3._node[0] = {}
+ self.K3._node[1] = {}
+ self.K3._node[2] = {}
+
+ def test_pickle(self):
+ G = self.K3
+ pg = pickle.loads(pickle.dumps(G, -1))
+ self.graphs_equal(pg, G)
+ pg = pickle.loads(pickle.dumps(G))
+ self.graphs_equal(pg, G)
+
+ def test_data_input(self):
+ G = self.Graph({1: [2], 2: [1]}, name="test")
+ assert G.name == "test"
+ assert sorted(G.adj.items()) == [(1, {2: {}}), (2, {1: {}})]
+
+ def test_adjacency(self):
+ G = self.K3
+ assert dict(G.adjacency()) == {
+ 0: {1: {}, 2: {}},
+ 1: {0: {}, 2: {}},
+ 2: {0: {}, 1: {}},
+ }
+
+ def test_getitem(self):
+ G = self.K3
+ assert G.adj[0] == {1: {}, 2: {}}
+ assert G[0] == {1: {}, 2: {}}
+ with pytest.raises(KeyError):
+ G.__getitem__("j")
+ with pytest.raises(TypeError):
+ G.__getitem__(["A"])
+
+ def test_add_node(self):
+ G = self.Graph()
+ G.add_node(0)
+ assert G.adj == {0: {}}
+ # test add attributes
+ G.add_node(1, c="red")
+ G.add_node(2, c="blue")
+ G.add_node(3, c="red")
+ assert G.nodes[1]["c"] == "red"
+ assert G.nodes[2]["c"] == "blue"
+ assert G.nodes[3]["c"] == "red"
+ # test updating attributes
+ G.add_node(1, c="blue")
+ G.add_node(2, c="red")
+ G.add_node(3, c="blue")
+ assert G.nodes[1]["c"] == "blue"
+ assert G.nodes[2]["c"] == "red"
+ assert G.nodes[3]["c"] == "blue"
+
+ def test_add_nodes_from(self):
+ G = self.Graph()
+ G.add_nodes_from([0, 1, 2])
+ assert G.adj == {0: {}, 1: {}, 2: {}}
+ # test add attributes
+ G.add_nodes_from([0, 1, 2], c="red")
+ assert G.nodes[0]["c"] == "red"
+ assert G.nodes[2]["c"] == "red"
+ # test that attribute dicts are not the same
+ assert G.nodes[0] is not G.nodes[1]
+ # test updating attributes
+ G.add_nodes_from([0, 1, 2], c="blue")
+ assert G.nodes[0]["c"] == "blue"
+ assert G.nodes[2]["c"] == "blue"
+ assert G.nodes[0] is not G.nodes[1]
+ # test tuple input
+ H = self.Graph()
+ H.add_nodes_from(G.nodes(data=True))
+ assert H.nodes[0]["c"] == "blue"
+ assert H.nodes[2]["c"] == "blue"
+ assert H.nodes[0] is not H.nodes[1]
+ # specific overrides general
+ H.add_nodes_from([0, (1, {"c": "green"}), (3, {"c": "cyan"})], c="red")
+ assert H.nodes[0]["c"] == "red"
+ assert H.nodes[1]["c"] == "green"
+ assert H.nodes[2]["c"] == "blue"
+ assert H.nodes[3]["c"] == "cyan"
+
+ def test_remove_node(self):
+ G = self.K3.copy()
+ G.remove_node(0)
+ assert G.adj == {1: {2: {}}, 2: {1: {}}}
+ with pytest.raises(nx.NetworkXError):
+ G.remove_node(-1)
+
+ # generator here to implement list,set,string...
+
+ def test_remove_nodes_from(self):
+ G = self.K3.copy()
+ G.remove_nodes_from([0, 1])
+ assert G.adj == {2: {}}
+ G.remove_nodes_from([-1]) # silent fail
+
+ def test_add_edge(self):
+ G = self.Graph()
+ G.add_edge(0, 1)
+ assert G.adj == {0: {1: {}}, 1: {0: {}}}
+ G = self.Graph()
+ G.add_edge(*(0, 1))
+ assert G.adj == {0: {1: {}}, 1: {0: {}}}
+ G = self.Graph()
+ with pytest.raises(ValueError):
+ G.add_edge(None, "anything")
+
+ def test_add_edges_from(self):
+ G = self.Graph()
+ G.add_edges_from([(0, 1), (0, 2, {"weight": 3})])
+ assert G.adj == {
+ 0: {1: {}, 2: {"weight": 3}},
+ 1: {0: {}},
+ 2: {0: {"weight": 3}},
+ }
+ G = self.Graph()
+ G.add_edges_from([(0, 1), (0, 2, {"weight": 3}), (1, 2, {"data": 4})], data=2)
+ assert G.adj == {
+ 0: {1: {"data": 2}, 2: {"weight": 3, "data": 2}},
+ 1: {0: {"data": 2}, 2: {"data": 4}},
+ 2: {0: {"weight": 3, "data": 2}, 1: {"data": 4}},
+ }
+
+ with pytest.raises(nx.NetworkXError):
+ G.add_edges_from([(0,)]) # too few in tuple
+ with pytest.raises(nx.NetworkXError):
+ G.add_edges_from([(0, 1, 2, 3)]) # too many in tuple
+ with pytest.raises(TypeError):
+ G.add_edges_from([0]) # not a tuple
+ with pytest.raises(ValueError):
+ G.add_edges_from([(None, 3), (3, 2)]) # None cannot be a node
+
+ def test_remove_edge(self):
+ G = self.K3.copy()
+ G.remove_edge(0, 1)
+ assert G.adj == {0: {2: {}}, 1: {2: {}}, 2: {0: {}, 1: {}}}
+ with pytest.raises(nx.NetworkXError):
+ G.remove_edge(-1, 0)
+
+ def test_remove_edges_from(self):
+ G = self.K3.copy()
+ G.remove_edges_from([(0, 1)])
+ assert G.adj == {0: {2: {}}, 1: {2: {}}, 2: {0: {}, 1: {}}}
+ G.remove_edges_from([(0, 0)]) # silent fail
+
+ def test_clear(self):
+ G = self.K3.copy()
+ G.graph["name"] = "K3"
+ G.clear()
+ assert list(G.nodes) == []
+ assert G.adj == {}
+ assert G.graph == {}
+
+ def test_clear_edges(self):
+ G = self.K3.copy()
+ G.graph["name"] = "K3"
+ nodes = list(G.nodes)
+ G.clear_edges()
+ assert list(G.nodes) == nodes
+ assert G.adj == {0: {}, 1: {}, 2: {}}
+ assert list(G.edges) == []
+ assert G.graph["name"] == "K3"
+
+ def test_edges_data(self):
+ G = self.K3
+ all_edges = [(0, 1, {}), (0, 2, {}), (1, 2, {})]
+ assert edges_equal(G.edges(data=True), all_edges)
+ assert edges_equal(G.edges(0, data=True), [(0, 1, {}), (0, 2, {})])
+ assert edges_equal(G.edges([0, 1], data=True), all_edges)
+ with pytest.raises(nx.NetworkXError):
+ G.edges(-1, True)
+
+ def test_get_edge_data(self):
+ G = self.K3.copy()
+ assert G.get_edge_data(0, 1) == {}
+ assert G[0][1] == {}
+ assert G.get_edge_data(10, 20) is None
+ assert G.get_edge_data(-1, 0) is None
+ assert G.get_edge_data(-1, 0, default=1) == 1
+
+ def test_update(self):
+ # specify both edges and nodes
+ G = self.K3.copy()
+ G.update(nodes=[3, (4, {"size": 2})], edges=[(4, 5), (6, 7, {"weight": 2})])
+ nlist = [
+ (0, {}),
+ (1, {}),
+ (2, {}),
+ (3, {}),
+ (4, {"size": 2}),
+ (5, {}),
+ (6, {}),
+ (7, {}),
+ ]
+ assert sorted(G.nodes.data()) == nlist
+ if G.is_directed():
+ elist = [
+ (0, 1, {}),
+ (0, 2, {}),
+ (1, 0, {}),
+ (1, 2, {}),
+ (2, 0, {}),
+ (2, 1, {}),
+ (4, 5, {}),
+ (6, 7, {"weight": 2}),
+ ]
+ else:
+ elist = [
+ (0, 1, {}),
+ (0, 2, {}),
+ (1, 2, {}),
+ (4, 5, {}),
+ (6, 7, {"weight": 2}),
+ ]
+ assert sorted(G.edges.data()) == elist
+ assert G.graph == {}
+
+ # no keywords -- order is edges, nodes
+ G = self.K3.copy()
+ G.update([(4, 5), (6, 7, {"weight": 2})], [3, (4, {"size": 2})])
+ assert sorted(G.nodes.data()) == nlist
+ assert sorted(G.edges.data()) == elist
+ assert G.graph == {}
+
+ # update using only a graph
+ G = self.Graph()
+ G.graph["foo"] = "bar"
+ G.add_node(2, data=4)
+ G.add_edge(0, 1, weight=0.5)
+ GG = G.copy()
+ H = self.Graph()
+ GG.update(H)
+ assert graphs_equal(G, GG)
+ H.update(G)
+ assert graphs_equal(H, G)
+
+ # update nodes only
+ H = self.Graph()
+ H.update(nodes=[3, 4])
+ assert H.nodes ^ {3, 4} == set()
+ assert H.size() == 0
+
+ # update edges only
+ H = self.Graph()
+ H.update(edges=[(3, 4)])
+ assert sorted(H.edges.data()) == [(3, 4, {})]
+ assert H.size() == 1
+
+ # No inputs -> exception
+ with pytest.raises(nx.NetworkXError):
+ nx.Graph().update()
+
+
+class TestEdgeSubgraph:
+ """Unit tests for the :meth:`Graph.edge_subgraph` method."""
+
+ def setup_method(self):
+ # Create a path graph on five nodes.
+ G = nx.path_graph(5)
+ # Add some node, edge, and graph attributes.
+ for i in range(5):
+ G.nodes[i]["name"] = f"node{i}"
+ G.edges[0, 1]["name"] = "edge01"
+ G.edges[3, 4]["name"] = "edge34"
+ G.graph["name"] = "graph"
+ # Get the subgraph induced by the first and last edges.
+ self.G = G
+ self.H = G.edge_subgraph([(0, 1), (3, 4)])
+
+ def test_correct_nodes(self):
+ """Tests that the subgraph has the correct nodes."""
+ assert [0, 1, 3, 4] == sorted(self.H.nodes())
+
+ def test_correct_edges(self):
+ """Tests that the subgraph has the correct edges."""
+ assert [(0, 1, "edge01"), (3, 4, "edge34")] == sorted(self.H.edges(data="name"))
+
+ def test_add_node(self):
+ """Tests that adding a node to the original graph does not
+ affect the nodes of the subgraph.
+
+ """
+ self.G.add_node(5)
+ assert [0, 1, 3, 4] == sorted(self.H.nodes())
+
+ def test_remove_node(self):
+ """Tests that removing a node in the original graph does
+ affect the nodes of the subgraph.
+
+ """
+ self.G.remove_node(0)
+ assert [1, 3, 4] == sorted(self.H.nodes())
+
+ def test_node_attr_dict(self):
+ """Tests that the node attribute dictionary of the two graphs is
+ the same object.
+
+ """
+ for v in self.H:
+ assert self.G.nodes[v] == self.H.nodes[v]
+ # Making a change to G should make a change in H and vice versa.
+ self.G.nodes[0]["name"] = "foo"
+ assert self.G.nodes[0] == self.H.nodes[0]
+ self.H.nodes[1]["name"] = "bar"
+ assert self.G.nodes[1] == self.H.nodes[1]
+
+ def test_edge_attr_dict(self):
+ """Tests that the edge attribute dictionary of the two graphs is
+ the same object.
+
+ """
+ for u, v in self.H.edges():
+ assert self.G.edges[u, v] == self.H.edges[u, v]
+ # Making a change to G should make a change in H and vice versa.
+ self.G.edges[0, 1]["name"] = "foo"
+ assert self.G.edges[0, 1]["name"] == self.H.edges[0, 1]["name"]
+ self.H.edges[3, 4]["name"] = "bar"
+ assert self.G.edges[3, 4]["name"] == self.H.edges[3, 4]["name"]
+
+ def test_graph_attr_dict(self):
+ """Tests that the graph attribute dictionary of the two graphs
+ is the same object.
+
+ """
+ assert self.G.graph is self.H.graph
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_graph_historical.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_graph_historical.py
new file mode 100644
index 00000000..36aba710
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_graph_historical.py
@@ -0,0 +1,13 @@
+"""Original NetworkX graph tests"""
+
+import networkx
+import networkx as nx
+
+from .historical_tests import HistoricalTests
+
+
+class TestGraphHistorical(HistoricalTests):
+ @classmethod
+ def setup_class(cls):
+ HistoricalTests.setup_class()
+ cls.G = nx.Graph
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_graphviews.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_graphviews.py
new file mode 100644
index 00000000..591c760c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_graphviews.py
@@ -0,0 +1,350 @@
+import pytest
+
+import networkx as nx
+from networkx.utils import edges_equal, nodes_equal
+
+# Note: SubGraph views are not tested here. They have their own testing file
+
+
+class TestReverseView:
+ def setup_method(self):
+ self.G = nx.path_graph(9, create_using=nx.DiGraph())
+ self.rv = nx.reverse_view(self.G)
+
+ def test_pickle(self):
+ import pickle
+
+ rv = self.rv
+ prv = pickle.loads(pickle.dumps(rv, -1))
+ assert rv._node == prv._node
+ assert rv._adj == prv._adj
+ assert rv.graph == prv.graph
+
+ def test_contains(self):
+ assert (2, 3) in self.G.edges
+ assert (3, 2) not in self.G.edges
+ assert (2, 3) not in self.rv.edges
+ assert (3, 2) in self.rv.edges
+
+ def test_iter(self):
+ expected = sorted(tuple(reversed(e)) for e in self.G.edges)
+ assert sorted(self.rv.edges) == expected
+
+ def test_exceptions(self):
+ G = nx.Graph()
+ pytest.raises(nx.NetworkXNotImplemented, nx.reverse_view, G)
+
+ def test_subclass(self):
+ class MyGraph(nx.DiGraph):
+ def my_method(self):
+ return "me"
+
+ def to_directed_class(self):
+ return MyGraph()
+
+ M = MyGraph()
+ M.add_edge(1, 2)
+ RM = nx.reverse_view(M)
+ print("RM class", RM.__class__)
+ RMC = RM.copy()
+ print("RMC class", RMC.__class__)
+ print(RMC.edges)
+ assert RMC.has_edge(2, 1)
+ assert RMC.my_method() == "me"
+
+
+class TestMultiReverseView:
+ def setup_method(self):
+ self.G = nx.path_graph(9, create_using=nx.MultiDiGraph())
+ self.G.add_edge(4, 5)
+ self.rv = nx.reverse_view(self.G)
+
+ def test_pickle(self):
+ import pickle
+
+ rv = self.rv
+ prv = pickle.loads(pickle.dumps(rv, -1))
+ assert rv._node == prv._node
+ assert rv._adj == prv._adj
+ assert rv.graph == prv.graph
+
+ def test_contains(self):
+ assert (2, 3, 0) in self.G.edges
+ assert (3, 2, 0) not in self.G.edges
+ assert (2, 3, 0) not in self.rv.edges
+ assert (3, 2, 0) in self.rv.edges
+ assert (5, 4, 1) in self.rv.edges
+ assert (4, 5, 1) not in self.rv.edges
+
+ def test_iter(self):
+ expected = sorted((v, u, k) for u, v, k in self.G.edges)
+ assert sorted(self.rv.edges) == expected
+
+ def test_exceptions(self):
+ MG = nx.MultiGraph(self.G)
+ pytest.raises(nx.NetworkXNotImplemented, nx.reverse_view, MG)
+
+
+def test_generic_multitype():
+ nxg = nx.graphviews
+ G = nx.DiGraph([(1, 2)])
+ with pytest.raises(nx.NetworkXError):
+ nxg.generic_graph_view(G, create_using=nx.MultiGraph)
+ G = nx.MultiDiGraph([(1, 2)])
+ with pytest.raises(nx.NetworkXError):
+ nxg.generic_graph_view(G, create_using=nx.DiGraph)
+
+
+class TestToDirected:
+ def setup_method(self):
+ self.G = nx.path_graph(9)
+ self.dv = nx.to_directed(self.G)
+ self.MG = nx.path_graph(9, create_using=nx.MultiGraph())
+ self.Mdv = nx.to_directed(self.MG)
+
+ def test_directed(self):
+ assert not self.G.is_directed()
+ assert self.dv.is_directed()
+
+ def test_already_directed(self):
+ dd = nx.to_directed(self.dv)
+ Mdd = nx.to_directed(self.Mdv)
+ assert edges_equal(dd.edges, self.dv.edges)
+ assert edges_equal(Mdd.edges, self.Mdv.edges)
+
+ def test_pickle(self):
+ import pickle
+
+ dv = self.dv
+ pdv = pickle.loads(pickle.dumps(dv, -1))
+ assert dv._node == pdv._node
+ assert dv._succ == pdv._succ
+ assert dv._pred == pdv._pred
+ assert dv.graph == pdv.graph
+
+ def test_contains(self):
+ assert (2, 3) in self.G.edges
+ assert (3, 2) in self.G.edges
+ assert (2, 3) in self.dv.edges
+ assert (3, 2) in self.dv.edges
+
+ def test_iter(self):
+ revd = [tuple(reversed(e)) for e in self.G.edges]
+ expected = sorted(list(self.G.edges) + revd)
+ assert sorted(self.dv.edges) == expected
+
+
+class TestToUndirected:
+ def setup_method(self):
+ self.DG = nx.path_graph(9, create_using=nx.DiGraph())
+ self.uv = nx.to_undirected(self.DG)
+ self.MDG = nx.path_graph(9, create_using=nx.MultiDiGraph())
+ self.Muv = nx.to_undirected(self.MDG)
+
+ def test_directed(self):
+ assert self.DG.is_directed()
+ assert not self.uv.is_directed()
+
+ def test_already_directed(self):
+ uu = nx.to_undirected(self.uv)
+ Muu = nx.to_undirected(self.Muv)
+ assert edges_equal(uu.edges, self.uv.edges)
+ assert edges_equal(Muu.edges, self.Muv.edges)
+
+ def test_pickle(self):
+ import pickle
+
+ uv = self.uv
+ puv = pickle.loads(pickle.dumps(uv, -1))
+ assert uv._node == puv._node
+ assert uv._adj == puv._adj
+ assert uv.graph == puv.graph
+ assert hasattr(uv, "_graph")
+
+ def test_contains(self):
+ assert (2, 3) in self.DG.edges
+ assert (3, 2) not in self.DG.edges
+ assert (2, 3) in self.uv.edges
+ assert (3, 2) in self.uv.edges
+
+ def test_iter(self):
+ expected = sorted(self.DG.edges)
+ assert sorted(self.uv.edges) == expected
+
+
+class TestChainsOfViews:
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9)
+ cls.DG = nx.path_graph(9, create_using=nx.DiGraph())
+ cls.MG = nx.path_graph(9, create_using=nx.MultiGraph())
+ cls.MDG = nx.path_graph(9, create_using=nx.MultiDiGraph())
+ cls.Gv = nx.to_undirected(cls.DG)
+ cls.DGv = nx.to_directed(cls.G)
+ cls.MGv = nx.to_undirected(cls.MDG)
+ cls.MDGv = nx.to_directed(cls.MG)
+ cls.Rv = cls.DG.reverse()
+ cls.MRv = cls.MDG.reverse()
+ cls.graphs = [
+ cls.G,
+ cls.DG,
+ cls.MG,
+ cls.MDG,
+ cls.Gv,
+ cls.DGv,
+ cls.MGv,
+ cls.MDGv,
+ cls.Rv,
+ cls.MRv,
+ ]
+ for G in cls.graphs:
+ G.edges, G.nodes, G.degree
+
+ def test_pickle(self):
+ import pickle
+
+ for G in self.graphs:
+ H = pickle.loads(pickle.dumps(G, -1))
+ assert edges_equal(H.edges, G.edges)
+ assert nodes_equal(H.nodes, G.nodes)
+
+ def test_subgraph_of_subgraph(self):
+ SGv = nx.subgraph(self.G, range(3, 7))
+ SDGv = nx.subgraph(self.DG, range(3, 7))
+ SMGv = nx.subgraph(self.MG, range(3, 7))
+ SMDGv = nx.subgraph(self.MDG, range(3, 7))
+ for G in self.graphs + [SGv, SDGv, SMGv, SMDGv]:
+ SG = nx.induced_subgraph(G, [4, 5, 6])
+ assert list(SG) == [4, 5, 6]
+ SSG = SG.subgraph([6, 7])
+ assert list(SSG) == [6]
+ # subgraph-subgraph chain is short-cut in base class method
+ assert SSG._graph is G
+
+ def test_restricted_induced_subgraph_chains(self):
+ """Test subgraph chains that both restrict and show nodes/edges.
+
+ A restricted_view subgraph should allow induced subgraphs using
+ G.subgraph that automagically without a chain (meaning the result
+ is a subgraph view of the original graph not a subgraph-of-subgraph.
+ """
+ hide_nodes = [3, 4, 5]
+ hide_edges = [(6, 7)]
+ RG = nx.restricted_view(self.G, hide_nodes, hide_edges)
+ nodes = [4, 5, 6, 7, 8]
+ SG = nx.induced_subgraph(RG, nodes)
+ SSG = RG.subgraph(nodes)
+ assert RG._graph is self.G
+ assert SSG._graph is self.G
+ assert SG._graph is RG
+ assert edges_equal(SG.edges, SSG.edges)
+ # should be same as morphing the graph
+ CG = self.G.copy()
+ CG.remove_nodes_from(hide_nodes)
+ CG.remove_edges_from(hide_edges)
+ assert edges_equal(CG.edges(nodes), SSG.edges)
+ CG.remove_nodes_from([0, 1, 2, 3])
+ assert edges_equal(CG.edges, SSG.edges)
+ # switch order: subgraph first, then restricted view
+ SSSG = self.G.subgraph(nodes)
+ RSG = nx.restricted_view(SSSG, hide_nodes, hide_edges)
+ assert RSG._graph is not self.G
+ assert edges_equal(RSG.edges, CG.edges)
+
+ def test_subgraph_copy(self):
+ for origG in self.graphs:
+ G = nx.Graph(origG)
+ SG = G.subgraph([4, 5, 6])
+ H = SG.copy()
+ assert type(G) == type(H)
+
+ def test_subgraph_todirected(self):
+ SG = nx.induced_subgraph(self.G, [4, 5, 6])
+ SSG = SG.to_directed()
+ assert sorted(SSG) == [4, 5, 6]
+ assert sorted(SSG.edges) == [(4, 5), (5, 4), (5, 6), (6, 5)]
+
+ def test_subgraph_toundirected(self):
+ SG = nx.induced_subgraph(self.G, [4, 5, 6])
+ SSG = SG.to_undirected()
+ assert list(SSG) == [4, 5, 6]
+ assert sorted(SSG.edges) == [(4, 5), (5, 6)]
+
+ def test_reverse_subgraph_toundirected(self):
+ G = self.DG.reverse(copy=False)
+ SG = G.subgraph([4, 5, 6])
+ SSG = SG.to_undirected()
+ assert list(SSG) == [4, 5, 6]
+ assert sorted(SSG.edges) == [(4, 5), (5, 6)]
+
+ def test_reverse_reverse_copy(self):
+ G = self.DG.reverse(copy=False)
+ H = G.reverse(copy=True)
+ assert H.nodes == self.DG.nodes
+ assert H.edges == self.DG.edges
+ G = self.MDG.reverse(copy=False)
+ H = G.reverse(copy=True)
+ assert H.nodes == self.MDG.nodes
+ assert H.edges == self.MDG.edges
+
+ def test_subgraph_edgesubgraph_toundirected(self):
+ G = self.G.copy()
+ SG = G.subgraph([4, 5, 6])
+ SSG = SG.edge_subgraph([(4, 5), (5, 4)])
+ USSG = SSG.to_undirected()
+ assert list(USSG) == [4, 5]
+ assert sorted(USSG.edges) == [(4, 5)]
+
+ def test_copy_subgraph(self):
+ G = self.G.copy()
+ SG = G.subgraph([4, 5, 6])
+ CSG = SG.copy(as_view=True)
+ DCSG = SG.copy(as_view=False)
+ assert hasattr(CSG, "_graph") # is a view
+ assert not hasattr(DCSG, "_graph") # not a view
+
+ def test_copy_disubgraph(self):
+ G = self.DG.copy()
+ SG = G.subgraph([4, 5, 6])
+ CSG = SG.copy(as_view=True)
+ DCSG = SG.copy(as_view=False)
+ assert hasattr(CSG, "_graph") # is a view
+ assert not hasattr(DCSG, "_graph") # not a view
+
+ def test_copy_multidisubgraph(self):
+ G = self.MDG.copy()
+ SG = G.subgraph([4, 5, 6])
+ CSG = SG.copy(as_view=True)
+ DCSG = SG.copy(as_view=False)
+ assert hasattr(CSG, "_graph") # is a view
+ assert not hasattr(DCSG, "_graph") # not a view
+
+ def test_copy_multisubgraph(self):
+ G = self.MG.copy()
+ SG = G.subgraph([4, 5, 6])
+ CSG = SG.copy(as_view=True)
+ DCSG = SG.copy(as_view=False)
+ assert hasattr(CSG, "_graph") # is a view
+ assert not hasattr(DCSG, "_graph") # not a view
+
+ def test_copy_of_view(self):
+ G = nx.MultiGraph(self.MGv)
+ assert G.__class__.__name__ == "MultiGraph"
+ G = G.copy(as_view=True)
+ assert G.__class__.__name__ == "MultiGraph"
+
+ def test_subclass(self):
+ class MyGraph(nx.DiGraph):
+ def my_method(self):
+ return "me"
+
+ def to_directed_class(self):
+ return MyGraph()
+
+ for origG in self.graphs:
+ G = MyGraph(origG)
+ SG = G.subgraph([4, 5, 6])
+ H = SG.copy()
+ assert SG.my_method() == "me"
+ assert H.my_method() == "me"
+ assert 3 not in H or 3 in SG
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_multidigraph.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_multidigraph.py
new file mode 100644
index 00000000..fc0bd546
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_multidigraph.py
@@ -0,0 +1,459 @@
+from collections import UserDict
+
+import pytest
+
+import networkx as nx
+from networkx.utils import edges_equal
+
+from .test_multigraph import BaseMultiGraphTester
+from .test_multigraph import TestEdgeSubgraph as _TestMultiGraphEdgeSubgraph
+from .test_multigraph import TestMultiGraph as _TestMultiGraph
+
+
+class BaseMultiDiGraphTester(BaseMultiGraphTester):
+ def test_edges(self):
+ G = self.K3
+ edges = [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
+ assert sorted(G.edges()) == edges
+ assert sorted(G.edges(0)) == [(0, 1), (0, 2)]
+ pytest.raises((KeyError, nx.NetworkXError), G.edges, -1)
+
+ def test_edges_data(self):
+ G = self.K3
+ edges = [(0, 1, {}), (0, 2, {}), (1, 0, {}), (1, 2, {}), (2, 0, {}), (2, 1, {})]
+ assert sorted(G.edges(data=True)) == edges
+ assert sorted(G.edges(0, data=True)) == [(0, 1, {}), (0, 2, {})]
+ pytest.raises((KeyError, nx.NetworkXError), G.neighbors, -1)
+
+ def test_edges_multi(self):
+ G = self.K3
+ assert sorted(G.edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
+ assert sorted(G.edges(0)) == [(0, 1), (0, 2)]
+ G.add_edge(0, 1)
+ assert sorted(G.edges()) == [
+ (0, 1),
+ (0, 1),
+ (0, 2),
+ (1, 0),
+ (1, 2),
+ (2, 0),
+ (2, 1),
+ ]
+
+ def test_out_edges(self):
+ G = self.K3
+ assert sorted(G.out_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
+ assert sorted(G.out_edges(0)) == [(0, 1), (0, 2)]
+ pytest.raises((KeyError, nx.NetworkXError), G.out_edges, -1)
+ assert sorted(G.out_edges(0, keys=True)) == [(0, 1, 0), (0, 2, 0)]
+
+ def test_out_edges_multi(self):
+ G = self.K3
+ assert sorted(G.out_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
+ assert sorted(G.out_edges(0)) == [(0, 1), (0, 2)]
+ G.add_edge(0, 1, 2)
+ assert sorted(G.out_edges()) == [
+ (0, 1),
+ (0, 1),
+ (0, 2),
+ (1, 0),
+ (1, 2),
+ (2, 0),
+ (2, 1),
+ ]
+
+ def test_out_edges_data(self):
+ G = self.K3
+ assert sorted(G.edges(0, data=True)) == [(0, 1, {}), (0, 2, {})]
+ G.remove_edge(0, 1)
+ G.add_edge(0, 1, data=1)
+ assert sorted(G.edges(0, data=True)) == [(0, 1, {"data": 1}), (0, 2, {})]
+ assert sorted(G.edges(0, data="data")) == [(0, 1, 1), (0, 2, None)]
+ assert sorted(G.edges(0, data="data", default=-1)) == [(0, 1, 1), (0, 2, -1)]
+
+ def test_in_edges(self):
+ G = self.K3
+ assert sorted(G.in_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
+ assert sorted(G.in_edges(0)) == [(1, 0), (2, 0)]
+ pytest.raises((KeyError, nx.NetworkXError), G.in_edges, -1)
+ G.add_edge(0, 1, 2)
+ assert sorted(G.in_edges()) == [
+ (0, 1),
+ (0, 1),
+ (0, 2),
+ (1, 0),
+ (1, 2),
+ (2, 0),
+ (2, 1),
+ ]
+ assert sorted(G.in_edges(0, keys=True)) == [(1, 0, 0), (2, 0, 0)]
+
+ def test_in_edges_no_keys(self):
+ G = self.K3
+ assert sorted(G.in_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
+ assert sorted(G.in_edges(0)) == [(1, 0), (2, 0)]
+ G.add_edge(0, 1, 2)
+ assert sorted(G.in_edges()) == [
+ (0, 1),
+ (0, 1),
+ (0, 2),
+ (1, 0),
+ (1, 2),
+ (2, 0),
+ (2, 1),
+ ]
+
+ assert sorted(G.in_edges(data=True, keys=False)) == [
+ (0, 1, {}),
+ (0, 1, {}),
+ (0, 2, {}),
+ (1, 0, {}),
+ (1, 2, {}),
+ (2, 0, {}),
+ (2, 1, {}),
+ ]
+
+ def test_in_edges_data(self):
+ G = self.K3
+ assert sorted(G.in_edges(0, data=True)) == [(1, 0, {}), (2, 0, {})]
+ G.remove_edge(1, 0)
+ G.add_edge(1, 0, data=1)
+ assert sorted(G.in_edges(0, data=True)) == [(1, 0, {"data": 1}), (2, 0, {})]
+ assert sorted(G.in_edges(0, data="data")) == [(1, 0, 1), (2, 0, None)]
+ assert sorted(G.in_edges(0, data="data", default=-1)) == [(1, 0, 1), (2, 0, -1)]
+
+ def is_shallow(self, H, G):
+ # graph
+ assert G.graph["foo"] == H.graph["foo"]
+ G.graph["foo"].append(1)
+ assert G.graph["foo"] == H.graph["foo"]
+ # node
+ assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
+ G.nodes[0]["foo"].append(1)
+ assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
+ # edge
+ assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
+ G[1][2][0]["foo"].append(1)
+ assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
+
+ def is_deep(self, H, G):
+ # graph
+ assert G.graph["foo"] == H.graph["foo"]
+ G.graph["foo"].append(1)
+ assert G.graph["foo"] != H.graph["foo"]
+ # node
+ assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
+ G.nodes[0]["foo"].append(1)
+ assert G.nodes[0]["foo"] != H.nodes[0]["foo"]
+ # edge
+ assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
+ G[1][2][0]["foo"].append(1)
+ assert G[1][2][0]["foo"] != H[1][2][0]["foo"]
+
+ def test_to_undirected(self):
+ # MultiDiGraph -> MultiGraph changes number of edges so it is
+ # not a copy operation... use is_shallow, not is_shallow_copy
+ G = self.K3
+ self.add_attributes(G)
+ H = nx.MultiGraph(G)
+ # self.is_shallow(H,G)
+ # the result is traversal order dependent so we
+ # can't use the is_shallow() test here.
+ try:
+ assert edges_equal(H.edges(), [(0, 1), (1, 2), (2, 0)])
+ except AssertionError:
+ assert edges_equal(H.edges(), [(0, 1), (1, 2), (1, 2), (2, 0)])
+ H = G.to_undirected()
+ self.is_deep(H, G)
+
+ def test_has_successor(self):
+ G = self.K3
+ assert G.has_successor(0, 1)
+ assert not G.has_successor(0, -1)
+
+ def test_successors(self):
+ G = self.K3
+ assert sorted(G.successors(0)) == [1, 2]
+ pytest.raises((KeyError, nx.NetworkXError), G.successors, -1)
+
+ def test_has_predecessor(self):
+ G = self.K3
+ assert G.has_predecessor(0, 1)
+ assert not G.has_predecessor(0, -1)
+
+ def test_predecessors(self):
+ G = self.K3
+ assert sorted(G.predecessors(0)) == [1, 2]
+ pytest.raises((KeyError, nx.NetworkXError), G.predecessors, -1)
+
+ def test_degree(self):
+ G = self.K3
+ assert sorted(G.degree()) == [(0, 4), (1, 4), (2, 4)]
+ assert dict(G.degree()) == {0: 4, 1: 4, 2: 4}
+ assert G.degree(0) == 4
+ assert list(G.degree(iter([0]))) == [(0, 4)]
+ G.add_edge(0, 1, weight=0.3, other=1.2)
+ assert sorted(G.degree(weight="weight")) == [(0, 4.3), (1, 4.3), (2, 4)]
+ assert sorted(G.degree(weight="other")) == [(0, 5.2), (1, 5.2), (2, 4)]
+
+ def test_in_degree(self):
+ G = self.K3
+ assert sorted(G.in_degree()) == [(0, 2), (1, 2), (2, 2)]
+ assert dict(G.in_degree()) == {0: 2, 1: 2, 2: 2}
+ assert G.in_degree(0) == 2
+ assert list(G.in_degree(iter([0]))) == [(0, 2)]
+ assert G.in_degree(0, weight="weight") == 2
+
+ def test_out_degree(self):
+ G = self.K3
+ assert sorted(G.out_degree()) == [(0, 2), (1, 2), (2, 2)]
+ assert dict(G.out_degree()) == {0: 2, 1: 2, 2: 2}
+ assert G.out_degree(0) == 2
+ assert list(G.out_degree(iter([0]))) == [(0, 2)]
+ assert G.out_degree(0, weight="weight") == 2
+
+ def test_size(self):
+ G = self.K3
+ assert G.size() == 6
+ assert G.number_of_edges() == 6
+ G.add_edge(0, 1, weight=0.3, other=1.2)
+ assert round(G.size(weight="weight"), 2) == 6.3
+ assert round(G.size(weight="other"), 2) == 7.2
+
+ def test_to_undirected_reciprocal(self):
+ G = self.Graph()
+ G.add_edge(1, 2)
+ assert G.to_undirected().has_edge(1, 2)
+ assert not G.to_undirected(reciprocal=True).has_edge(1, 2)
+ G.add_edge(2, 1)
+ assert G.to_undirected(reciprocal=True).has_edge(1, 2)
+
+ def test_reverse_copy(self):
+ G = nx.MultiDiGraph([(0, 1), (0, 1)])
+ R = G.reverse()
+ assert sorted(R.edges()) == [(1, 0), (1, 0)]
+ R.remove_edge(1, 0)
+ assert sorted(R.edges()) == [(1, 0)]
+ assert sorted(G.edges()) == [(0, 1), (0, 1)]
+
+ def test_reverse_nocopy(self):
+ G = nx.MultiDiGraph([(0, 1), (0, 1)])
+ R = G.reverse(copy=False)
+ assert sorted(R.edges()) == [(1, 0), (1, 0)]
+ pytest.raises(nx.NetworkXError, R.remove_edge, 1, 0)
+
+ def test_di_attributes_cached(self):
+ G = self.K3.copy()
+ assert id(G.in_edges) == id(G.in_edges)
+ assert id(G.out_edges) == id(G.out_edges)
+ assert id(G.in_degree) == id(G.in_degree)
+ assert id(G.out_degree) == id(G.out_degree)
+ assert id(G.succ) == id(G.succ)
+ assert id(G.pred) == id(G.pred)
+
+
+class TestMultiDiGraph(BaseMultiDiGraphTester, _TestMultiGraph):
+ def setup_method(self):
+ self.Graph = nx.MultiDiGraph
+ # build K3
+ self.k3edges = [(0, 1), (0, 2), (1, 2)]
+ self.k3nodes = [0, 1, 2]
+ self.K3 = self.Graph()
+ self.K3._succ = {0: {}, 1: {}, 2: {}}
+ # K3._adj is synced with K3._succ
+ self.K3._pred = {0: {}, 1: {}, 2: {}}
+ for u in self.k3nodes:
+ for v in self.k3nodes:
+ if u == v:
+ continue
+ d = {0: {}}
+ self.K3._succ[u][v] = d
+ self.K3._pred[v][u] = d
+ self.K3._node = {}
+ self.K3._node[0] = {}
+ self.K3._node[1] = {}
+ self.K3._node[2] = {}
+
+ def test_add_edge(self):
+ G = self.Graph()
+ G.add_edge(0, 1)
+ assert G._adj == {0: {1: {0: {}}}, 1: {}}
+ assert G._succ == {0: {1: {0: {}}}, 1: {}}
+ assert G._pred == {0: {}, 1: {0: {0: {}}}}
+ G = self.Graph()
+ G.add_edge(*(0, 1))
+ assert G._adj == {0: {1: {0: {}}}, 1: {}}
+ assert G._succ == {0: {1: {0: {}}}, 1: {}}
+ assert G._pred == {0: {}, 1: {0: {0: {}}}}
+ with pytest.raises(ValueError, match="None cannot be a node"):
+ G.add_edge(None, 3)
+
+ def test_add_edges_from(self):
+ G = self.Graph()
+ G.add_edges_from([(0, 1), (0, 1, {"weight": 3})])
+ assert G._adj == {0: {1: {0: {}, 1: {"weight": 3}}}, 1: {}}
+ assert G._succ == {0: {1: {0: {}, 1: {"weight": 3}}}, 1: {}}
+ assert G._pred == {0: {}, 1: {0: {0: {}, 1: {"weight": 3}}}}
+
+ G.add_edges_from([(0, 1), (0, 1, {"weight": 3})], weight=2)
+ assert G._succ == {
+ 0: {1: {0: {}, 1: {"weight": 3}, 2: {"weight": 2}, 3: {"weight": 3}}},
+ 1: {},
+ }
+ assert G._pred == {
+ 0: {},
+ 1: {0: {0: {}, 1: {"weight": 3}, 2: {"weight": 2}, 3: {"weight": 3}}},
+ }
+
+ G = self.Graph()
+ edges = [
+ (0, 1, {"weight": 3}),
+ (0, 1, (("weight", 2),)),
+ (0, 1, 5),
+ (0, 1, "s"),
+ ]
+ G.add_edges_from(edges)
+ keydict = {0: {"weight": 3}, 1: {"weight": 2}, 5: {}, "s": {}}
+ assert G._succ == {0: {1: keydict}, 1: {}}
+ assert G._pred == {1: {0: keydict}, 0: {}}
+
+ # too few in tuple
+ pytest.raises(nx.NetworkXError, G.add_edges_from, [(0,)])
+ # too many in tuple
+ pytest.raises(nx.NetworkXError, G.add_edges_from, [(0, 1, 2, 3, 4)])
+ # not a tuple
+ pytest.raises(TypeError, G.add_edges_from, [0])
+ with pytest.raises(ValueError, match="None cannot be a node"):
+ G.add_edges_from([(None, 3), (3, 2)])
+
+ def test_remove_edge(self):
+ G = self.K3
+ G.remove_edge(0, 1)
+ assert G._succ == {
+ 0: {2: {0: {}}},
+ 1: {0: {0: {}}, 2: {0: {}}},
+ 2: {0: {0: {}}, 1: {0: {}}},
+ }
+ assert G._pred == {
+ 0: {1: {0: {}}, 2: {0: {}}},
+ 1: {2: {0: {}}},
+ 2: {0: {0: {}}, 1: {0: {}}},
+ }
+ pytest.raises((KeyError, nx.NetworkXError), G.remove_edge, -1, 0)
+ pytest.raises((KeyError, nx.NetworkXError), G.remove_edge, 0, 2, key=1)
+
+ def test_remove_multiedge(self):
+ G = self.K3
+ G.add_edge(0, 1, key="parallel edge")
+ G.remove_edge(0, 1, key="parallel edge")
+ assert G._adj == {
+ 0: {1: {0: {}}, 2: {0: {}}},
+ 1: {0: {0: {}}, 2: {0: {}}},
+ 2: {0: {0: {}}, 1: {0: {}}},
+ }
+
+ assert G._succ == {
+ 0: {1: {0: {}}, 2: {0: {}}},
+ 1: {0: {0: {}}, 2: {0: {}}},
+ 2: {0: {0: {}}, 1: {0: {}}},
+ }
+
+ assert G._pred == {
+ 0: {1: {0: {}}, 2: {0: {}}},
+ 1: {0: {0: {}}, 2: {0: {}}},
+ 2: {0: {0: {}}, 1: {0: {}}},
+ }
+ G.remove_edge(0, 1)
+ assert G._succ == {
+ 0: {2: {0: {}}},
+ 1: {0: {0: {}}, 2: {0: {}}},
+ 2: {0: {0: {}}, 1: {0: {}}},
+ }
+ assert G._pred == {
+ 0: {1: {0: {}}, 2: {0: {}}},
+ 1: {2: {0: {}}},
+ 2: {0: {0: {}}, 1: {0: {}}},
+ }
+ pytest.raises((KeyError, nx.NetworkXError), G.remove_edge, -1, 0)
+
+ def test_remove_edges_from(self):
+ G = self.K3
+ G.remove_edges_from([(0, 1)])
+ assert G._succ == {
+ 0: {2: {0: {}}},
+ 1: {0: {0: {}}, 2: {0: {}}},
+ 2: {0: {0: {}}, 1: {0: {}}},
+ }
+ assert G._pred == {
+ 0: {1: {0: {}}, 2: {0: {}}},
+ 1: {2: {0: {}}},
+ 2: {0: {0: {}}, 1: {0: {}}},
+ }
+ G.remove_edges_from([(0, 0)]) # silent fail
+
+
+class TestEdgeSubgraph(_TestMultiGraphEdgeSubgraph):
+ """Unit tests for the :meth:`MultiDiGraph.edge_subgraph` method."""
+
+ def setup_method(self):
+ # Create a quadruply-linked path graph on five nodes.
+ G = nx.MultiDiGraph()
+ nx.add_path(G, range(5))
+ nx.add_path(G, range(5))
+ nx.add_path(G, reversed(range(5)))
+ nx.add_path(G, reversed(range(5)))
+ # Add some node, edge, and graph attributes.
+ for i in range(5):
+ G.nodes[i]["name"] = f"node{i}"
+ G.adj[0][1][0]["name"] = "edge010"
+ G.adj[0][1][1]["name"] = "edge011"
+ G.adj[3][4][0]["name"] = "edge340"
+ G.adj[3][4][1]["name"] = "edge341"
+ G.graph["name"] = "graph"
+ # Get the subgraph induced by one of the first edges and one of
+ # the last edges.
+ self.G = G
+ self.H = G.edge_subgraph([(0, 1, 0), (3, 4, 1)])
+
+
+class CustomDictClass(UserDict):
+ pass
+
+
+class MultiDiGraphSubClass(nx.MultiDiGraph):
+ node_dict_factory = CustomDictClass # type: ignore[assignment]
+ node_attr_dict_factory = CustomDictClass # type: ignore[assignment]
+ adjlist_outer_dict_factory = CustomDictClass # type: ignore[assignment]
+ adjlist_inner_dict_factory = CustomDictClass # type: ignore[assignment]
+ edge_key_dict_factory = CustomDictClass # type: ignore[assignment]
+ edge_attr_dict_factory = CustomDictClass # type: ignore[assignment]
+ graph_attr_dict_factory = CustomDictClass # type: ignore[assignment]
+
+
+class TestMultiDiGraphSubclass(TestMultiDiGraph):
+ def setup_method(self):
+ self.Graph = MultiDiGraphSubClass
+ # build K3
+ self.k3edges = [(0, 1), (0, 2), (1, 2)]
+ self.k3nodes = [0, 1, 2]
+ self.K3 = self.Graph()
+ self.K3._succ = self.K3.adjlist_outer_dict_factory(
+ {
+ 0: self.K3.adjlist_inner_dict_factory(),
+ 1: self.K3.adjlist_inner_dict_factory(),
+ 2: self.K3.adjlist_inner_dict_factory(),
+ }
+ )
+ # K3._adj is synced with K3._succ
+ self.K3._pred = {0: {}, 1: {}, 2: {}}
+ for u in self.k3nodes:
+ for v in self.k3nodes:
+ if u == v:
+ continue
+ d = {0: {}}
+ self.K3._succ[u][v] = d
+ self.K3._pred[v][u] = d
+ self.K3._node = self.K3.node_dict_factory()
+ self.K3._node[0] = self.K3.node_attr_dict_factory()
+ self.K3._node[1] = self.K3.node_attr_dict_factory()
+ self.K3._node[2] = self.K3.node_attr_dict_factory()
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_multigraph.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_multigraph.py
new file mode 100644
index 00000000..cd912d1d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_multigraph.py
@@ -0,0 +1,528 @@
+from collections import UserDict
+
+import pytest
+
+import networkx as nx
+from networkx.utils import edges_equal
+
+from .test_graph import BaseAttrGraphTester
+from .test_graph import TestGraph as _TestGraph
+
+
+class BaseMultiGraphTester(BaseAttrGraphTester):
+ def test_has_edge(self):
+ G = self.K3
+ assert G.has_edge(0, 1)
+ assert not G.has_edge(0, -1)
+ assert G.has_edge(0, 1, 0)
+ assert not G.has_edge(0, 1, 1)
+
+ def test_get_edge_data(self):
+ G = self.K3
+ assert G.get_edge_data(0, 1) == {0: {}}
+ assert G[0][1] == {0: {}}
+ assert G[0][1][0] == {}
+ assert G.get_edge_data(10, 20) is None
+ assert G.get_edge_data(0, 1, 0) == {}
+
+ def test_adjacency(self):
+ G = self.K3
+ assert dict(G.adjacency()) == {
+ 0: {1: {0: {}}, 2: {0: {}}},
+ 1: {0: {0: {}}, 2: {0: {}}},
+ 2: {0: {0: {}}, 1: {0: {}}},
+ }
+
+ def deepcopy_edge_attr(self, H, G):
+ assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
+ G[1][2][0]["foo"].append(1)
+ assert G[1][2][0]["foo"] != H[1][2][0]["foo"]
+
+ def shallow_copy_edge_attr(self, H, G):
+ assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
+ G[1][2][0]["foo"].append(1)
+ assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
+
+ def graphs_equal(self, H, G):
+ assert G._adj == H._adj
+ assert G._node == H._node
+ assert G.graph == H.graph
+ assert G.name == H.name
+ if not G.is_directed() and not H.is_directed():
+ assert H._adj[1][2][0] is H._adj[2][1][0]
+ assert G._adj[1][2][0] is G._adj[2][1][0]
+ else: # at least one is directed
+ if not G.is_directed():
+ G._pred = G._adj
+ G._succ = G._adj
+ if not H.is_directed():
+ H._pred = H._adj
+ H._succ = H._adj
+ assert G._pred == H._pred
+ assert G._succ == H._succ
+ assert H._succ[1][2][0] is H._pred[2][1][0]
+ assert G._succ[1][2][0] is G._pred[2][1][0]
+
+ def same_attrdict(self, H, G):
+ # same attrdict in the edgedata
+ old_foo = H[1][2][0]["foo"]
+ H.adj[1][2][0]["foo"] = "baz"
+ assert G._adj == H._adj
+ H.adj[1][2][0]["foo"] = old_foo
+ assert G._adj == H._adj
+
+ old_foo = H.nodes[0]["foo"]
+ H.nodes[0]["foo"] = "baz"
+ assert G._node == H._node
+ H.nodes[0]["foo"] = old_foo
+ assert G._node == H._node
+
+ def different_attrdict(self, H, G):
+ # used by graph_equal_but_different
+ old_foo = H[1][2][0]["foo"]
+ H.adj[1][2][0]["foo"] = "baz"
+ assert G._adj != H._adj
+ H.adj[1][2][0]["foo"] = old_foo
+ assert G._adj == H._adj
+
+ old_foo = H.nodes[0]["foo"]
+ H.nodes[0]["foo"] = "baz"
+ assert G._node != H._node
+ H.nodes[0]["foo"] = old_foo
+ assert G._node == H._node
+
+ def test_to_undirected(self):
+ G = self.K3
+ self.add_attributes(G)
+ H = nx.MultiGraph(G)
+ self.is_shallow_copy(H, G)
+ H = G.to_undirected()
+ self.is_deepcopy(H, G)
+
+ def test_to_directed(self):
+ G = self.K3
+ self.add_attributes(G)
+ H = nx.MultiDiGraph(G)
+ self.is_shallow_copy(H, G)
+ H = G.to_directed()
+ self.is_deepcopy(H, G)
+
+ def test_number_of_edges_selfloops(self):
+ G = self.K3
+ G.add_edge(0, 0)
+ G.add_edge(0, 0)
+ G.add_edge(0, 0, key="parallel edge")
+ G.remove_edge(0, 0, key="parallel edge")
+ assert G.number_of_edges(0, 0) == 2
+ G.remove_edge(0, 0)
+ assert G.number_of_edges(0, 0) == 1
+
+ def test_edge_lookup(self):
+ G = self.Graph()
+ G.add_edge(1, 2, foo="bar")
+ G.add_edge(1, 2, "key", foo="biz")
+ assert edges_equal(G.edges[1, 2, 0], {"foo": "bar"})
+ assert edges_equal(G.edges[1, 2, "key"], {"foo": "biz"})
+
+ def test_edge_attr(self):
+ G = self.Graph()
+ G.add_edge(1, 2, key="k1", foo="bar")
+ G.add_edge(1, 2, key="k2", foo="baz")
+ assert isinstance(G.get_edge_data(1, 2), G.edge_key_dict_factory)
+ assert all(
+ isinstance(d, G.edge_attr_dict_factory) for u, v, d in G.edges(data=True)
+ )
+ assert edges_equal(
+ G.edges(keys=True, data=True),
+ [(1, 2, "k1", {"foo": "bar"}), (1, 2, "k2", {"foo": "baz"})],
+ )
+ assert edges_equal(
+ G.edges(keys=True, data="foo"), [(1, 2, "k1", "bar"), (1, 2, "k2", "baz")]
+ )
+
+ def test_edge_attr4(self):
+ G = self.Graph()
+ G.add_edge(1, 2, key=0, data=7, spam="bar", bar="foo")
+ assert edges_equal(
+ G.edges(data=True), [(1, 2, {"data": 7, "spam": "bar", "bar": "foo"})]
+ )
+ G[1][2][0]["data"] = 10 # OK to set data like this
+ assert edges_equal(
+ G.edges(data=True), [(1, 2, {"data": 10, "spam": "bar", "bar": "foo"})]
+ )
+
+ G.adj[1][2][0]["data"] = 20
+ assert edges_equal(
+ G.edges(data=True), [(1, 2, {"data": 20, "spam": "bar", "bar": "foo"})]
+ )
+ G.edges[1, 2, 0]["data"] = 21 # another spelling, "edge"
+ assert edges_equal(
+ G.edges(data=True), [(1, 2, {"data": 21, "spam": "bar", "bar": "foo"})]
+ )
+ G.adj[1][2][0]["listdata"] = [20, 200]
+ G.adj[1][2][0]["weight"] = 20
+ assert edges_equal(
+ G.edges(data=True),
+ [
+ (
+ 1,
+ 2,
+ {
+ "data": 21,
+ "spam": "bar",
+ "bar": "foo",
+ "listdata": [20, 200],
+ "weight": 20,
+ },
+ )
+ ],
+ )
+
+
+class TestMultiGraph(BaseMultiGraphTester, _TestGraph):
+ def setup_method(self):
+ self.Graph = nx.MultiGraph
+ # build K3
+ ed1, ed2, ed3 = ({0: {}}, {0: {}}, {0: {}})
+ self.k3adj = {0: {1: ed1, 2: ed2}, 1: {0: ed1, 2: ed3}, 2: {0: ed2, 1: ed3}}
+ self.k3edges = [(0, 1), (0, 2), (1, 2)]
+ self.k3nodes = [0, 1, 2]
+ self.K3 = self.Graph()
+ self.K3._adj = self.k3adj
+ self.K3._node = {}
+ self.K3._node[0] = {}
+ self.K3._node[1] = {}
+ self.K3._node[2] = {}
+
+ def test_data_input(self):
+ G = self.Graph({1: [2], 2: [1]}, name="test")
+ assert G.name == "test"
+ expected = [(1, {2: {0: {}}}), (2, {1: {0: {}}})]
+ assert sorted(G.adj.items()) == expected
+
+ def test_data_multigraph_input(self):
+ # standard case with edge keys and edge data
+ edata0 = {"w": 200, "s": "foo"}
+ edata1 = {"w": 201, "s": "bar"}
+ keydict = {0: edata0, 1: edata1}
+ dododod = {"a": {"b": keydict}}
+
+ multiple_edge = [("a", "b", 0, edata0), ("a", "b", 1, edata1)]
+ single_edge = [("a", "b", 0, keydict)]
+
+ G = self.Graph(dododod, multigraph_input=True)
+ assert list(G.edges(keys=True, data=True)) == multiple_edge
+ G = self.Graph(dododod, multigraph_input=None)
+ assert list(G.edges(keys=True, data=True)) == multiple_edge
+ G = self.Graph(dododod, multigraph_input=False)
+ assert list(G.edges(keys=True, data=True)) == single_edge
+
+ # test round-trip to_dict_of_dict and MultiGraph constructor
+ G = self.Graph(dododod, multigraph_input=True)
+ H = self.Graph(nx.to_dict_of_dicts(G))
+ assert nx.is_isomorphic(G, H) is True # test that default is True
+ for mgi in [True, False]:
+ H = self.Graph(nx.to_dict_of_dicts(G), multigraph_input=mgi)
+ assert nx.is_isomorphic(G, H) == mgi
+
+ # Set up cases for when incoming_graph_data is not multigraph_input
+ etraits = {"w": 200, "s": "foo"}
+ egraphics = {"color": "blue", "shape": "box"}
+ edata = {"traits": etraits, "graphics": egraphics}
+ dodod1 = {"a": {"b": edata}}
+ dodod2 = {"a": {"b": etraits}}
+ dodod3 = {"a": {"b": {"traits": etraits, "s": "foo"}}}
+ dol = {"a": ["b"]}
+
+ multiple_edge = [("a", "b", "traits", etraits), ("a", "b", "graphics", egraphics)]
+ single_edge = [("a", "b", 0, {})] # type: ignore[var-annotated]
+ single_edge1 = [("a", "b", 0, edata)]
+ single_edge2 = [("a", "b", 0, etraits)]
+ single_edge3 = [("a", "b", 0, {"traits": etraits, "s": "foo"})]
+
+ cases = [ # (dod, mgi, edges)
+ (dodod1, True, multiple_edge),
+ (dodod1, False, single_edge1),
+ (dodod2, False, single_edge2),
+ (dodod3, False, single_edge3),
+ (dol, False, single_edge),
+ ]
+
+ @pytest.mark.parametrize("dod, mgi, edges", cases)
+ def test_non_multigraph_input(self, dod, mgi, edges):
+ G = self.Graph(dod, multigraph_input=mgi)
+ assert list(G.edges(keys=True, data=True)) == edges
+ G = nx.to_networkx_graph(dod, create_using=self.Graph, multigraph_input=mgi)
+ assert list(G.edges(keys=True, data=True)) == edges
+
+ mgi_none_cases = [
+ (dodod1, multiple_edge),
+ (dodod2, single_edge2),
+ (dodod3, single_edge3),
+ ]
+
+ @pytest.mark.parametrize("dod, edges", mgi_none_cases)
+ def test_non_multigraph_input_mgi_none(self, dod, edges):
+ # test constructor without to_networkx_graph for mgi=None
+ G = self.Graph(dod)
+ assert list(G.edges(keys=True, data=True)) == edges
+
+ raise_cases = [dodod2, dodod3, dol]
+
+ @pytest.mark.parametrize("dod", raise_cases)
+ def test_non_multigraph_input_raise(self, dod):
+ # cases where NetworkXError is raised
+ pytest.raises(nx.NetworkXError, self.Graph, dod, multigraph_input=True)
+ pytest.raises(
+ nx.NetworkXError,
+ nx.to_networkx_graph,
+ dod,
+ create_using=self.Graph,
+ multigraph_input=True,
+ )
+
+ def test_getitem(self):
+ G = self.K3
+ assert G[0] == {1: {0: {}}, 2: {0: {}}}
+ with pytest.raises(KeyError):
+ G.__getitem__("j")
+ with pytest.raises(TypeError):
+ G.__getitem__(["A"])
+
+ def test_remove_node(self):
+ G = self.K3
+ G.remove_node(0)
+ assert G.adj == {1: {2: {0: {}}}, 2: {1: {0: {}}}}
+ with pytest.raises(nx.NetworkXError):
+ G.remove_node(-1)
+
+ def test_add_edge(self):
+ G = self.Graph()
+ G.add_edge(0, 1)
+ assert G.adj == {0: {1: {0: {}}}, 1: {0: {0: {}}}}
+ G = self.Graph()
+ G.add_edge(*(0, 1))
+ assert G.adj == {0: {1: {0: {}}}, 1: {0: {0: {}}}}
+ G = self.Graph()
+ with pytest.raises(ValueError):
+ G.add_edge(None, "anything")
+
+ def test_add_edge_conflicting_key(self):
+ G = self.Graph()
+ G.add_edge(0, 1, key=1)
+ G.add_edge(0, 1)
+ assert G.number_of_edges() == 2
+ G = self.Graph()
+ G.add_edges_from([(0, 1, 1, {})])
+ G.add_edges_from([(0, 1)])
+ assert G.number_of_edges() == 2
+
+ def test_add_edges_from(self):
+ G = self.Graph()
+ G.add_edges_from([(0, 1), (0, 1, {"weight": 3})])
+ assert G.adj == {
+ 0: {1: {0: {}, 1: {"weight": 3}}},
+ 1: {0: {0: {}, 1: {"weight": 3}}},
+ }
+ G.add_edges_from([(0, 1), (0, 1, {"weight": 3})], weight=2)
+ assert G.adj == {
+ 0: {1: {0: {}, 1: {"weight": 3}, 2: {"weight": 2}, 3: {"weight": 3}}},
+ 1: {0: {0: {}, 1: {"weight": 3}, 2: {"weight": 2}, 3: {"weight": 3}}},
+ }
+ G = self.Graph()
+ edges = [
+ (0, 1, {"weight": 3}),
+ (0, 1, (("weight", 2),)),
+ (0, 1, 5),
+ (0, 1, "s"),
+ ]
+ G.add_edges_from(edges)
+ keydict = {0: {"weight": 3}, 1: {"weight": 2}, 5: {}, "s": {}}
+ assert G._adj == {0: {1: keydict}, 1: {0: keydict}}
+
+ # too few in tuple
+ with pytest.raises(nx.NetworkXError):
+ G.add_edges_from([(0,)])
+ # too many in tuple
+ with pytest.raises(nx.NetworkXError):
+ G.add_edges_from([(0, 1, 2, 3, 4)])
+ # not a tuple
+ with pytest.raises(TypeError):
+ G.add_edges_from([0])
+
+ def test_multigraph_add_edges_from_four_tuple_misordered(self):
+ """add_edges_from expects 4-tuples of the format (u, v, key, data_dict).
+
+ Ensure 4-tuples of form (u, v, data_dict, key) raise exception.
+ """
+ G = nx.MultiGraph()
+ with pytest.raises(TypeError):
+ # key/data values flipped in 4-tuple
+ G.add_edges_from([(0, 1, {"color": "red"}, 0)])
+
+ def test_remove_edge(self):
+ G = self.K3
+ G.remove_edge(0, 1)
+ assert G.adj == {0: {2: {0: {}}}, 1: {2: {0: {}}}, 2: {0: {0: {}}, 1: {0: {}}}}
+
+ with pytest.raises(nx.NetworkXError):
+ G.remove_edge(-1, 0)
+ with pytest.raises(nx.NetworkXError):
+ G.remove_edge(0, 2, key=1)
+
+ def test_remove_edges_from(self):
+ G = self.K3.copy()
+ G.remove_edges_from([(0, 1)])
+ kd = {0: {}}
+ assert G.adj == {0: {2: kd}, 1: {2: kd}, 2: {0: kd, 1: kd}}
+ G.remove_edges_from([(0, 0)]) # silent fail
+ self.K3.add_edge(0, 1)
+ G = self.K3.copy()
+ G.remove_edges_from(list(G.edges(data=True, keys=True)))
+ assert G.adj == {0: {}, 1: {}, 2: {}}
+ G = self.K3.copy()
+ G.remove_edges_from(list(G.edges(data=False, keys=True)))
+ assert G.adj == {0: {}, 1: {}, 2: {}}
+ G = self.K3.copy()
+ G.remove_edges_from(list(G.edges(data=False, keys=False)))
+ assert G.adj == {0: {}, 1: {}, 2: {}}
+ G = self.K3.copy()
+ G.remove_edges_from([(0, 1, 0), (0, 2, 0, {}), (1, 2)])
+ assert G.adj == {0: {1: {1: {}}}, 1: {0: {1: {}}}, 2: {}}
+
+ def test_remove_multiedge(self):
+ G = self.K3
+ G.add_edge(0, 1, key="parallel edge")
+ G.remove_edge(0, 1, key="parallel edge")
+ assert G.adj == {
+ 0: {1: {0: {}}, 2: {0: {}}},
+ 1: {0: {0: {}}, 2: {0: {}}},
+ 2: {0: {0: {}}, 1: {0: {}}},
+ }
+ G.remove_edge(0, 1)
+ kd = {0: {}}
+ assert G.adj == {0: {2: kd}, 1: {2: kd}, 2: {0: kd, 1: kd}}
+ with pytest.raises(nx.NetworkXError):
+ G.remove_edge(-1, 0)
+
+
+class TestEdgeSubgraph:
+ """Unit tests for the :meth:`MultiGraph.edge_subgraph` method."""
+
+ def setup_method(self):
+ # Create a doubly-linked path graph on five nodes.
+ G = nx.MultiGraph()
+ nx.add_path(G, range(5))
+ nx.add_path(G, range(5))
+ # Add some node, edge, and graph attributes.
+ for i in range(5):
+ G.nodes[i]["name"] = f"node{i}"
+ G.adj[0][1][0]["name"] = "edge010"
+ G.adj[0][1][1]["name"] = "edge011"
+ G.adj[3][4][0]["name"] = "edge340"
+ G.adj[3][4][1]["name"] = "edge341"
+ G.graph["name"] = "graph"
+ # Get the subgraph induced by one of the first edges and one of
+ # the last edges.
+ self.G = G
+ self.H = G.edge_subgraph([(0, 1, 0), (3, 4, 1)])
+
+ def test_correct_nodes(self):
+ """Tests that the subgraph has the correct nodes."""
+ assert [0, 1, 3, 4] == sorted(self.H.nodes())
+
+ def test_correct_edges(self):
+ """Tests that the subgraph has the correct edges."""
+ assert [(0, 1, 0, "edge010"), (3, 4, 1, "edge341")] == sorted(
+ self.H.edges(keys=True, data="name")
+ )
+
+ def test_add_node(self):
+ """Tests that adding a node to the original graph does not
+ affect the nodes of the subgraph.
+
+ """
+ self.G.add_node(5)
+ assert [0, 1, 3, 4] == sorted(self.H.nodes())
+
+ def test_remove_node(self):
+ """Tests that removing a node in the original graph does
+ affect the nodes of the subgraph.
+
+ """
+ self.G.remove_node(0)
+ assert [1, 3, 4] == sorted(self.H.nodes())
+
+ def test_node_attr_dict(self):
+ """Tests that the node attribute dictionary of the two graphs is
+ the same object.
+
+ """
+ for v in self.H:
+ assert self.G.nodes[v] == self.H.nodes[v]
+ # Making a change to G should make a change in H and vice versa.
+ self.G.nodes[0]["name"] = "foo"
+ assert self.G.nodes[0] == self.H.nodes[0]
+ self.H.nodes[1]["name"] = "bar"
+ assert self.G.nodes[1] == self.H.nodes[1]
+
+ def test_edge_attr_dict(self):
+ """Tests that the edge attribute dictionary of the two graphs is
+ the same object.
+
+ """
+ for u, v, k in self.H.edges(keys=True):
+ assert self.G._adj[u][v][k] == self.H._adj[u][v][k]
+ # Making a change to G should make a change in H and vice versa.
+ self.G._adj[0][1][0]["name"] = "foo"
+ assert self.G._adj[0][1][0]["name"] == self.H._adj[0][1][0]["name"]
+ self.H._adj[3][4][1]["name"] = "bar"
+ assert self.G._adj[3][4][1]["name"] == self.H._adj[3][4][1]["name"]
+
+ def test_graph_attr_dict(self):
+ """Tests that the graph attribute dictionary of the two graphs
+ is the same object.
+
+ """
+ assert self.G.graph is self.H.graph
+
+
+class CustomDictClass(UserDict):
+ pass
+
+
+class MultiGraphSubClass(nx.MultiGraph):
+ node_dict_factory = CustomDictClass # type: ignore[assignment]
+ node_attr_dict_factory = CustomDictClass # type: ignore[assignment]
+ adjlist_outer_dict_factory = CustomDictClass # type: ignore[assignment]
+ adjlist_inner_dict_factory = CustomDictClass # type: ignore[assignment]
+ edge_key_dict_factory = CustomDictClass # type: ignore[assignment]
+ edge_attr_dict_factory = CustomDictClass # type: ignore[assignment]
+ graph_attr_dict_factory = CustomDictClass # type: ignore[assignment]
+
+
+class TestMultiGraphSubclass(TestMultiGraph):
+ def setup_method(self):
+ self.Graph = MultiGraphSubClass
+ # build K3
+ self.k3edges = [(0, 1), (0, 2), (1, 2)]
+ self.k3nodes = [0, 1, 2]
+ self.K3 = self.Graph()
+ self.K3._adj = self.K3.adjlist_outer_dict_factory(
+ {
+ 0: self.K3.adjlist_inner_dict_factory(),
+ 1: self.K3.adjlist_inner_dict_factory(),
+ 2: self.K3.adjlist_inner_dict_factory(),
+ }
+ )
+ self.K3._pred = {0: {}, 1: {}, 2: {}}
+ for u in self.k3nodes:
+ for v in self.k3nodes:
+ if u != v:
+ d = {0: {}}
+ self.K3._adj[u][v] = d
+ self.K3._adj[v][u] = d
+ self.K3._node = self.K3.node_dict_factory()
+ self.K3._node[0] = self.K3.node_attr_dict_factory()
+ self.K3._node[1] = self.K3.node_attr_dict_factory()
+ self.K3._node[2] = self.K3.node_attr_dict_factory()
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_reportviews.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_reportviews.py
new file mode 100644
index 00000000..789c829f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_reportviews.py
@@ -0,0 +1,1435 @@
+import pickle
+from copy import deepcopy
+
+import pytest
+
+import networkx as nx
+from networkx.classes import reportviews as rv
+from networkx.classes.reportviews import NodeDataView
+
+
+# Nodes
+class TestNodeView:
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9)
+ cls.nv = cls.G.nodes # NodeView(G)
+
+ def test_pickle(self):
+ import pickle
+
+ nv = self.nv
+ pnv = pickle.loads(pickle.dumps(nv, -1))
+ assert nv == pnv
+ assert nv.__slots__ == pnv.__slots__
+
+ def test_str(self):
+ assert str(self.nv) == "[0, 1, 2, 3, 4, 5, 6, 7, 8]"
+
+ def test_repr(self):
+ assert repr(self.nv) == "NodeView((0, 1, 2, 3, 4, 5, 6, 7, 8))"
+
+ def test_contains(self):
+ G = self.G.copy()
+ nv = G.nodes
+ assert 7 in nv
+ assert 9 not in nv
+ G.remove_node(7)
+ G.add_node(9)
+ assert 7 not in nv
+ assert 9 in nv
+
+ def test_getitem(self):
+ G = self.G.copy()
+ nv = G.nodes
+ G.nodes[3]["foo"] = "bar"
+ assert nv[7] == {}
+ assert nv[3] == {"foo": "bar"}
+ # slicing
+ with pytest.raises(nx.NetworkXError):
+ G.nodes[0:5]
+
+ def test_iter(self):
+ nv = self.nv
+ for i, n in enumerate(nv):
+ assert i == n
+ inv = iter(nv)
+ assert next(inv) == 0
+ assert iter(nv) != nv
+ assert iter(inv) == inv
+ inv2 = iter(nv)
+ next(inv2)
+ assert list(inv) == list(inv2)
+ # odd case where NodeView calls NodeDataView with data=False
+ nnv = nv(data=False)
+ for i, n in enumerate(nnv):
+ assert i == n
+
+ def test_call(self):
+ nodes = self.nv
+ assert nodes is nodes()
+ assert nodes is not nodes(data=True)
+ assert nodes is not nodes(data="weight")
+
+
+class TestNodeDataView:
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9)
+ cls.nv = NodeDataView(cls.G)
+ cls.ndv = cls.G.nodes.data(True)
+ cls.nwv = cls.G.nodes.data("foo")
+
+ def test_viewtype(self):
+ nv = self.G.nodes
+ ndvfalse = nv.data(False)
+ assert nv is ndvfalse
+ assert nv is not self.ndv
+
+ def test_pickle(self):
+ import pickle
+
+ nv = self.nv
+ pnv = pickle.loads(pickle.dumps(nv, -1))
+ assert nv == pnv
+ assert nv.__slots__ == pnv.__slots__
+
+ def test_str(self):
+ msg = str([(n, {}) for n in range(9)])
+ assert str(self.ndv) == msg
+
+ def test_repr(self):
+ expected = "NodeDataView((0, 1, 2, 3, 4, 5, 6, 7, 8))"
+ assert repr(self.nv) == expected
+ expected = (
+ "NodeDataView({0: {}, 1: {}, 2: {}, 3: {}, "
+ + "4: {}, 5: {}, 6: {}, 7: {}, 8: {}})"
+ )
+ assert repr(self.ndv) == expected
+ expected = (
+ "NodeDataView({0: None, 1: None, 2: None, 3: None, 4: None, "
+ + "5: None, 6: None, 7: None, 8: None}, data='foo')"
+ )
+ assert repr(self.nwv) == expected
+
+ def test_contains(self):
+ G = self.G.copy()
+ nv = G.nodes.data()
+ nwv = G.nodes.data("foo")
+ G.nodes[3]["foo"] = "bar"
+ assert (7, {}) in nv
+ assert (3, {"foo": "bar"}) in nv
+ assert (3, "bar") in nwv
+ assert (7, None) in nwv
+ # default
+ nwv_def = G.nodes(data="foo", default="biz")
+ assert (7, "biz") in nwv_def
+ assert (3, "bar") in nwv_def
+
+ def test_getitem(self):
+ G = self.G.copy()
+ nv = G.nodes
+ G.nodes[3]["foo"] = "bar"
+ assert nv[3] == {"foo": "bar"}
+ # default
+ nwv_def = G.nodes(data="foo", default="biz")
+ assert nwv_def[7], "biz"
+ assert nwv_def[3] == "bar"
+ # slicing
+ with pytest.raises(nx.NetworkXError):
+ G.nodes.data()[0:5]
+
+ def test_iter(self):
+ G = self.G.copy()
+ nv = G.nodes.data()
+ ndv = G.nodes.data(True)
+ nwv = G.nodes.data("foo")
+ for i, (n, d) in enumerate(nv):
+ assert i == n
+ assert d == {}
+ inv = iter(nv)
+ assert next(inv) == (0, {})
+ G.nodes[3]["foo"] = "bar"
+ # default
+ for n, d in nv:
+ if n == 3:
+ assert d == {"foo": "bar"}
+ else:
+ assert d == {}
+ # data=True
+ for n, d in ndv:
+ if n == 3:
+ assert d == {"foo": "bar"}
+ else:
+ assert d == {}
+ # data='foo'
+ for n, d in nwv:
+ if n == 3:
+ assert d == "bar"
+ else:
+ assert d is None
+ # data='foo', default=1
+ for n, d in G.nodes.data("foo", default=1):
+ if n == 3:
+ assert d == "bar"
+ else:
+ assert d == 1
+
+
+def test_nodedataview_unhashable():
+ G = nx.path_graph(9)
+ G.nodes[3]["foo"] = "bar"
+ nvs = [G.nodes.data()]
+ nvs.append(G.nodes.data(True))
+ H = G.copy()
+ H.nodes[4]["foo"] = {1, 2, 3}
+ nvs.append(H.nodes.data(True))
+ # raise unhashable
+ for nv in nvs:
+ pytest.raises(TypeError, set, nv)
+ pytest.raises(TypeError, eval, "nv | nv", locals())
+ # no raise... hashable
+ Gn = G.nodes.data(False)
+ set(Gn)
+ Gn | Gn
+ Gn = G.nodes.data("foo")
+ set(Gn)
+ Gn | Gn
+
+
+class TestNodeViewSetOps:
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9)
+ cls.G.nodes[3]["foo"] = "bar"
+ cls.nv = cls.G.nodes
+
+ def n_its(self, nodes):
+ return set(nodes)
+
+ def test_len(self):
+ G = self.G.copy()
+ nv = G.nodes
+ assert len(nv) == 9
+ G.remove_node(7)
+ assert len(nv) == 8
+ G.add_node(9)
+ assert len(nv) == 9
+
+ def test_and(self):
+ # print("G & H nodes:", gnv & hnv)
+ nv = self.nv
+ some_nodes = self.n_its(range(5, 12))
+ assert nv & some_nodes == self.n_its(range(5, 9))
+ assert some_nodes & nv == self.n_its(range(5, 9))
+
+ def test_or(self):
+ # print("G | H nodes:", gnv | hnv)
+ nv = self.nv
+ some_nodes = self.n_its(range(5, 12))
+ assert nv | some_nodes == self.n_its(range(12))
+ assert some_nodes | nv == self.n_its(range(12))
+
+ def test_xor(self):
+ # print("G ^ H nodes:", gnv ^ hnv)
+ nv = self.nv
+ some_nodes = self.n_its(range(5, 12))
+ nodes = {0, 1, 2, 3, 4, 9, 10, 11}
+ assert nv ^ some_nodes == self.n_its(nodes)
+ assert some_nodes ^ nv == self.n_its(nodes)
+
+ def test_sub(self):
+ # print("G - H nodes:", gnv - hnv)
+ nv = self.nv
+ some_nodes = self.n_its(range(5, 12))
+ assert nv - some_nodes == self.n_its(range(5))
+ assert some_nodes - nv == self.n_its(range(9, 12))
+
+
+class TestNodeDataViewSetOps(TestNodeViewSetOps):
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9)
+ cls.G.nodes[3]["foo"] = "bar"
+ cls.nv = cls.G.nodes.data("foo")
+
+ def n_its(self, nodes):
+ return {(node, "bar" if node == 3 else None) for node in nodes}
+
+
+class TestNodeDataViewDefaultSetOps(TestNodeDataViewSetOps):
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9)
+ cls.G.nodes[3]["foo"] = "bar"
+ cls.nv = cls.G.nodes.data("foo", default=1)
+
+ def n_its(self, nodes):
+ return {(node, "bar" if node == 3 else 1) for node in nodes}
+
+
+# Edges Data View
+class TestEdgeDataView:
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9)
+ cls.eview = nx.reportviews.EdgeView
+
+ def test_pickle(self):
+ import pickle
+
+ ev = self.eview(self.G)(data=True)
+ pev = pickle.loads(pickle.dumps(ev, -1))
+ assert list(ev) == list(pev)
+ assert ev.__slots__ == pev.__slots__
+
+ def modify_edge(self, G, e, **kwds):
+ G._adj[e[0]][e[1]].update(kwds)
+
+ def test_str(self):
+ ev = self.eview(self.G)(data=True)
+ rep = str([(n, n + 1, {}) for n in range(8)])
+ assert str(ev) == rep
+
+ def test_repr(self):
+ ev = self.eview(self.G)(data=True)
+ rep = (
+ "EdgeDataView([(0, 1, {}), (1, 2, {}), "
+ + "(2, 3, {}), (3, 4, {}), "
+ + "(4, 5, {}), (5, 6, {}), "
+ + "(6, 7, {}), (7, 8, {})])"
+ )
+ assert repr(ev) == rep
+
+ def test_iterdata(self):
+ G = self.G.copy()
+ evr = self.eview(G)
+ ev = evr(data=True)
+ ev_def = evr(data="foo", default=1)
+
+ for u, v, d in ev:
+ pass
+ assert d == {}
+
+ for u, v, wt in ev_def:
+ pass
+ assert wt == 1
+
+ self.modify_edge(G, (2, 3), foo="bar")
+ for e in ev:
+ assert len(e) == 3
+ if set(e[:2]) == {2, 3}:
+ assert e[2] == {"foo": "bar"}
+ checked = True
+ else:
+ assert e[2] == {}
+ assert checked
+
+ for e in ev_def:
+ assert len(e) == 3
+ if set(e[:2]) == {2, 3}:
+ assert e[2] == "bar"
+ checked_wt = True
+ else:
+ assert e[2] == 1
+ assert checked_wt
+
+ def test_iter(self):
+ evr = self.eview(self.G)
+ ev = evr()
+ for u, v in ev:
+ pass
+ iev = iter(ev)
+ assert next(iev) == (0, 1)
+ assert iter(ev) != ev
+ assert iter(iev) == iev
+
+ def test_contains(self):
+ evr = self.eview(self.G)
+ ev = evr()
+ if self.G.is_directed():
+ assert (1, 2) in ev and (2, 1) not in ev
+ else:
+ assert (1, 2) in ev and (2, 1) in ev
+ assert (1, 4) not in ev
+ assert (1, 90) not in ev
+ assert (90, 1) not in ev
+
+ def test_contains_with_nbunch(self):
+ evr = self.eview(self.G)
+ ev = evr(nbunch=[0, 2])
+ if self.G.is_directed():
+ assert (0, 1) in ev
+ assert (1, 2) not in ev
+ assert (2, 3) in ev
+ else:
+ assert (0, 1) in ev
+ assert (1, 2) in ev
+ assert (2, 3) in ev
+ assert (3, 4) not in ev
+ assert (4, 5) not in ev
+ assert (5, 6) not in ev
+ assert (7, 8) not in ev
+ assert (8, 9) not in ev
+
+ def test_len(self):
+ evr = self.eview(self.G)
+ ev = evr(data="foo")
+ assert len(ev) == 8
+ assert len(evr(1)) == 2
+ assert len(evr([1, 2, 3])) == 4
+
+ assert len(self.G.edges(1)) == 2
+ assert len(self.G.edges()) == 8
+ assert len(self.G.edges) == 8
+
+ H = self.G.copy()
+ H.add_edge(1, 1)
+ assert len(H.edges(1)) == 3
+ assert len(H.edges()) == 9
+ assert len(H.edges) == 9
+
+
+class TestOutEdgeDataView(TestEdgeDataView):
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9, create_using=nx.DiGraph())
+ cls.eview = nx.reportviews.OutEdgeView
+
+ def test_repr(self):
+ ev = self.eview(self.G)(data=True)
+ rep = (
+ "OutEdgeDataView([(0, 1, {}), (1, 2, {}), "
+ + "(2, 3, {}), (3, 4, {}), "
+ + "(4, 5, {}), (5, 6, {}), "
+ + "(6, 7, {}), (7, 8, {})])"
+ )
+ assert repr(ev) == rep
+
+ def test_len(self):
+ evr = self.eview(self.G)
+ ev = evr(data="foo")
+ assert len(ev) == 8
+ assert len(evr(1)) == 1
+ assert len(evr([1, 2, 3])) == 3
+
+ assert len(self.G.edges(1)) == 1
+ assert len(self.G.edges()) == 8
+ assert len(self.G.edges) == 8
+
+ H = self.G.copy()
+ H.add_edge(1, 1)
+ assert len(H.edges(1)) == 2
+ assert len(H.edges()) == 9
+ assert len(H.edges) == 9
+
+ def test_contains_with_nbunch(self):
+ evr = self.eview(self.G)
+ ev = evr(nbunch=[0, 2])
+ assert (0, 1) in ev
+ assert (1, 2) not in ev
+ assert (2, 3) in ev
+ assert (3, 4) not in ev
+ assert (4, 5) not in ev
+ assert (5, 6) not in ev
+ assert (7, 8) not in ev
+ assert (8, 9) not in ev
+
+
+class TestInEdgeDataView(TestOutEdgeDataView):
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9, create_using=nx.DiGraph())
+ cls.eview = nx.reportviews.InEdgeView
+
+ def test_repr(self):
+ ev = self.eview(self.G)(data=True)
+ rep = (
+ "InEdgeDataView([(0, 1, {}), (1, 2, {}), "
+ + "(2, 3, {}), (3, 4, {}), "
+ + "(4, 5, {}), (5, 6, {}), "
+ + "(6, 7, {}), (7, 8, {})])"
+ )
+ assert repr(ev) == rep
+
+ def test_contains_with_nbunch(self):
+ evr = self.eview(self.G)
+ ev = evr(nbunch=[0, 2])
+ assert (0, 1) not in ev
+ assert (1, 2) in ev
+ assert (2, 3) not in ev
+ assert (3, 4) not in ev
+ assert (4, 5) not in ev
+ assert (5, 6) not in ev
+ assert (7, 8) not in ev
+ assert (8, 9) not in ev
+
+
+class TestMultiEdgeDataView(TestEdgeDataView):
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9, create_using=nx.MultiGraph())
+ cls.eview = nx.reportviews.MultiEdgeView
+
+ def modify_edge(self, G, e, **kwds):
+ G._adj[e[0]][e[1]][0].update(kwds)
+
+ def test_repr(self):
+ ev = self.eview(self.G)(data=True)
+ rep = (
+ "MultiEdgeDataView([(0, 1, {}), (1, 2, {}), "
+ + "(2, 3, {}), (3, 4, {}), "
+ + "(4, 5, {}), (5, 6, {}), "
+ + "(6, 7, {}), (7, 8, {})])"
+ )
+ assert repr(ev) == rep
+
+ def test_contains_with_nbunch(self):
+ evr = self.eview(self.G)
+ ev = evr(nbunch=[0, 2])
+ assert (0, 1) in ev
+ assert (1, 2) in ev
+ assert (2, 3) in ev
+ assert (3, 4) not in ev
+ assert (4, 5) not in ev
+ assert (5, 6) not in ev
+ assert (7, 8) not in ev
+ assert (8, 9) not in ev
+
+
+class TestOutMultiEdgeDataView(TestOutEdgeDataView):
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9, create_using=nx.MultiDiGraph())
+ cls.eview = nx.reportviews.OutMultiEdgeView
+
+ def modify_edge(self, G, e, **kwds):
+ G._adj[e[0]][e[1]][0].update(kwds)
+
+ def test_repr(self):
+ ev = self.eview(self.G)(data=True)
+ rep = (
+ "OutMultiEdgeDataView([(0, 1, {}), (1, 2, {}), "
+ + "(2, 3, {}), (3, 4, {}), "
+ + "(4, 5, {}), (5, 6, {}), "
+ + "(6, 7, {}), (7, 8, {})])"
+ )
+ assert repr(ev) == rep
+
+ def test_contains_with_nbunch(self):
+ evr = self.eview(self.G)
+ ev = evr(nbunch=[0, 2])
+ assert (0, 1) in ev
+ assert (1, 2) not in ev
+ assert (2, 3) in ev
+ assert (3, 4) not in ev
+ assert (4, 5) not in ev
+ assert (5, 6) not in ev
+ assert (7, 8) not in ev
+ assert (8, 9) not in ev
+
+
+class TestInMultiEdgeDataView(TestOutMultiEdgeDataView):
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9, create_using=nx.MultiDiGraph())
+ cls.eview = nx.reportviews.InMultiEdgeView
+
+ def test_repr(self):
+ ev = self.eview(self.G)(data=True)
+ rep = (
+ "InMultiEdgeDataView([(0, 1, {}), (1, 2, {}), "
+ + "(2, 3, {}), (3, 4, {}), "
+ + "(4, 5, {}), (5, 6, {}), "
+ + "(6, 7, {}), (7, 8, {})])"
+ )
+ assert repr(ev) == rep
+
+ def test_contains_with_nbunch(self):
+ evr = self.eview(self.G)
+ ev = evr(nbunch=[0, 2])
+ assert (0, 1) not in ev
+ assert (1, 2) in ev
+ assert (2, 3) not in ev
+ assert (3, 4) not in ev
+ assert (4, 5) not in ev
+ assert (5, 6) not in ev
+ assert (7, 8) not in ev
+ assert (8, 9) not in ev
+
+
+# Edge Views
+class TestEdgeView:
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9)
+ cls.eview = nx.reportviews.EdgeView
+
+ def test_pickle(self):
+ import pickle
+
+ ev = self.eview(self.G)
+ pev = pickle.loads(pickle.dumps(ev, -1))
+ assert ev == pev
+ assert ev.__slots__ == pev.__slots__
+
+ def modify_edge(self, G, e, **kwds):
+ G._adj[e[0]][e[1]].update(kwds)
+
+ def test_str(self):
+ ev = self.eview(self.G)
+ rep = str([(n, n + 1) for n in range(8)])
+ assert str(ev) == rep
+
+ def test_repr(self):
+ ev = self.eview(self.G)
+ rep = (
+ "EdgeView([(0, 1), (1, 2), (2, 3), (3, 4), "
+ + "(4, 5), (5, 6), (6, 7), (7, 8)])"
+ )
+ assert repr(ev) == rep
+
+ def test_getitem(self):
+ G = self.G.copy()
+ ev = G.edges
+ G.edges[0, 1]["foo"] = "bar"
+ assert ev[0, 1] == {"foo": "bar"}
+
+ # slicing
+ with pytest.raises(nx.NetworkXError, match=".*does not support slicing"):
+ G.edges[0:5]
+
+ # Invalid edge
+ with pytest.raises(KeyError, match=r".*edge.*is not in the graph."):
+ G.edges[0, 9]
+
+ def test_call(self):
+ ev = self.eview(self.G)
+ assert id(ev) == id(ev())
+ assert id(ev) == id(ev(data=False))
+ assert id(ev) != id(ev(data=True))
+ assert id(ev) != id(ev(nbunch=1))
+
+ def test_data(self):
+ ev = self.eview(self.G)
+ assert id(ev) != id(ev.data())
+ assert id(ev) == id(ev.data(data=False))
+ assert id(ev) != id(ev.data(data=True))
+ assert id(ev) != id(ev.data(nbunch=1))
+
+ def test_iter(self):
+ ev = self.eview(self.G)
+ for u, v in ev:
+ pass
+ iev = iter(ev)
+ assert next(iev) == (0, 1)
+ assert iter(ev) != ev
+ assert iter(iev) == iev
+
+ def test_contains(self):
+ ev = self.eview(self.G)
+ edv = ev()
+ if self.G.is_directed():
+ assert (1, 2) in ev and (2, 1) not in ev
+ assert (1, 2) in edv and (2, 1) not in edv
+ else:
+ assert (1, 2) in ev and (2, 1) in ev
+ assert (1, 2) in edv and (2, 1) in edv
+ assert (1, 4) not in ev
+ assert (1, 4) not in edv
+ # edge not in graph
+ assert (1, 90) not in ev
+ assert (90, 1) not in ev
+ assert (1, 90) not in edv
+ assert (90, 1) not in edv
+
+ def test_contains_with_nbunch(self):
+ ev = self.eview(self.G)
+ evn = ev(nbunch=[0, 2])
+ assert (0, 1) in evn
+ assert (1, 2) in evn
+ assert (2, 3) in evn
+ assert (3, 4) not in evn
+ assert (4, 5) not in evn
+ assert (5, 6) not in evn
+ assert (7, 8) not in evn
+ assert (8, 9) not in evn
+
+ def test_len(self):
+ ev = self.eview(self.G)
+ num_ed = 9 if self.G.is_multigraph() else 8
+ assert len(ev) == num_ed
+
+ H = self.G.copy()
+ H.add_edge(1, 1)
+ assert len(H.edges(1)) == 3 + H.is_multigraph() - H.is_directed()
+ assert len(H.edges()) == num_ed + 1
+ assert len(H.edges) == num_ed + 1
+
+ def test_and(self):
+ # print("G & H edges:", gnv & hnv)
+ ev = self.eview(self.G)
+ some_edges = {(0, 1), (1, 0), (0, 2)}
+ if self.G.is_directed():
+ assert some_edges & ev, {(0, 1)}
+ assert ev & some_edges, {(0, 1)}
+ else:
+ assert ev & some_edges == {(0, 1), (1, 0)}
+ assert some_edges & ev == {(0, 1), (1, 0)}
+ return
+
+ def test_or(self):
+ # print("G | H edges:", gnv | hnv)
+ ev = self.eview(self.G)
+ some_edges = {(0, 1), (1, 0), (0, 2)}
+ result1 = {(n, n + 1) for n in range(8)}
+ result1.update(some_edges)
+ result2 = {(n + 1, n) for n in range(8)}
+ result2.update(some_edges)
+ assert (ev | some_edges) in (result1, result2)
+ assert (some_edges | ev) in (result1, result2)
+
+ def test_xor(self):
+ # print("G ^ H edges:", gnv ^ hnv)
+ ev = self.eview(self.G)
+ some_edges = {(0, 1), (1, 0), (0, 2)}
+ if self.G.is_directed():
+ result = {(n, n + 1) for n in range(1, 8)}
+ result.update({(1, 0), (0, 2)})
+ assert ev ^ some_edges == result
+ else:
+ result = {(n, n + 1) for n in range(1, 8)}
+ result.update({(0, 2)})
+ assert ev ^ some_edges == result
+ return
+
+ def test_sub(self):
+ # print("G - H edges:", gnv - hnv)
+ ev = self.eview(self.G)
+ some_edges = {(0, 1), (1, 0), (0, 2)}
+ result = {(n, n + 1) for n in range(8)}
+ result.remove((0, 1))
+ assert ev - some_edges, result
+
+
+class TestOutEdgeView(TestEdgeView):
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9, nx.DiGraph())
+ cls.eview = nx.reportviews.OutEdgeView
+
+ def test_repr(self):
+ ev = self.eview(self.G)
+ rep = (
+ "OutEdgeView([(0, 1), (1, 2), (2, 3), (3, 4), "
+ + "(4, 5), (5, 6), (6, 7), (7, 8)])"
+ )
+ assert repr(ev) == rep
+
+ def test_contains_with_nbunch(self):
+ ev = self.eview(self.G)
+ evn = ev(nbunch=[0, 2])
+ assert (0, 1) in evn
+ assert (1, 2) not in evn
+ assert (2, 3) in evn
+ assert (3, 4) not in evn
+ assert (4, 5) not in evn
+ assert (5, 6) not in evn
+ assert (7, 8) not in evn
+ assert (8, 9) not in evn
+
+
+class TestInEdgeView(TestEdgeView):
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9, nx.DiGraph())
+ cls.eview = nx.reportviews.InEdgeView
+
+ def test_repr(self):
+ ev = self.eview(self.G)
+ rep = (
+ "InEdgeView([(0, 1), (1, 2), (2, 3), (3, 4), "
+ + "(4, 5), (5, 6), (6, 7), (7, 8)])"
+ )
+ assert repr(ev) == rep
+
+ def test_contains_with_nbunch(self):
+ ev = self.eview(self.G)
+ evn = ev(nbunch=[0, 2])
+ assert (0, 1) not in evn
+ assert (1, 2) in evn
+ assert (2, 3) not in evn
+ assert (3, 4) not in evn
+ assert (4, 5) not in evn
+ assert (5, 6) not in evn
+ assert (7, 8) not in evn
+ assert (8, 9) not in evn
+
+
+class TestMultiEdgeView(TestEdgeView):
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9, nx.MultiGraph())
+ cls.G.add_edge(1, 2, key=3, foo="bar")
+ cls.eview = nx.reportviews.MultiEdgeView
+
+ def modify_edge(self, G, e, **kwds):
+ if len(e) == 2:
+ e = e + (0,)
+ G._adj[e[0]][e[1]][e[2]].update(kwds)
+
+ def test_str(self):
+ ev = self.eview(self.G)
+ replist = [(n, n + 1, 0) for n in range(8)]
+ replist.insert(2, (1, 2, 3))
+ rep = str(replist)
+ assert str(ev) == rep
+
+ def test_getitem(self):
+ G = self.G.copy()
+ ev = G.edges
+ G.edges[0, 1, 0]["foo"] = "bar"
+ assert ev[0, 1, 0] == {"foo": "bar"}
+
+ # slicing
+ with pytest.raises(nx.NetworkXError):
+ G.edges[0:5]
+
+ def test_repr(self):
+ ev = self.eview(self.G)
+ rep = (
+ "MultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 3), (2, 3, 0), "
+ + "(3, 4, 0), (4, 5, 0), (5, 6, 0), (6, 7, 0), (7, 8, 0)])"
+ )
+ assert repr(ev) == rep
+
+ def test_call(self):
+ ev = self.eview(self.G)
+ assert id(ev) == id(ev(keys=True))
+ assert id(ev) == id(ev(data=False, keys=True))
+ assert id(ev) != id(ev(keys=False))
+ assert id(ev) != id(ev(data=True))
+ assert id(ev) != id(ev(nbunch=1))
+
+ def test_data(self):
+ ev = self.eview(self.G)
+ assert id(ev) != id(ev.data())
+ assert id(ev) == id(ev.data(data=False, keys=True))
+ assert id(ev) != id(ev.data(keys=False))
+ assert id(ev) != id(ev.data(data=True))
+ assert id(ev) != id(ev.data(nbunch=1))
+
+ def test_iter(self):
+ ev = self.eview(self.G)
+ for u, v, k in ev:
+ pass
+ iev = iter(ev)
+ assert next(iev) == (0, 1, 0)
+ assert iter(ev) != ev
+ assert iter(iev) == iev
+
+ def test_iterkeys(self):
+ G = self.G
+ evr = self.eview(G)
+ ev = evr(keys=True)
+ for u, v, k in ev:
+ pass
+ assert k == 0
+ ev = evr(keys=True, data="foo", default=1)
+ for u, v, k, wt in ev:
+ pass
+ assert wt == 1
+
+ self.modify_edge(G, (2, 3, 0), foo="bar")
+ ev = evr(keys=True, data=True)
+ for e in ev:
+ assert len(e) == 4
+ print("edge:", e)
+ if set(e[:2]) == {2, 3}:
+ print(self.G._adj[2][3])
+ assert e[2] == 0
+ assert e[3] == {"foo": "bar"}
+ checked = True
+ elif set(e[:3]) == {1, 2, 3}:
+ assert e[2] == 3
+ assert e[3] == {"foo": "bar"}
+ checked_multi = True
+ else:
+ assert e[2] == 0
+ assert e[3] == {}
+ assert checked
+ assert checked_multi
+ ev = evr(keys=True, data="foo", default=1)
+ for e in ev:
+ if set(e[:2]) == {1, 2} and e[2] == 3:
+ assert e[3] == "bar"
+ if set(e[:2]) == {1, 2} and e[2] == 0:
+ assert e[3] == 1
+ if set(e[:2]) == {2, 3}:
+ assert e[2] == 0
+ assert e[3] == "bar"
+ assert len(e) == 4
+ checked_wt = True
+ assert checked_wt
+ ev = evr(keys=True)
+ for e in ev:
+ assert len(e) == 3
+ elist = sorted([(i, i + 1, 0) for i in range(8)] + [(1, 2, 3)])
+ assert sorted(ev) == elist
+ # test that the keyword arguments are passed correctly
+ ev = evr((1, 2), "foo", keys=True, default=1)
+ with pytest.raises(TypeError):
+ evr((1, 2), "foo", True, 1)
+ with pytest.raises(TypeError):
+ evr((1, 2), "foo", True, default=1)
+ for e in ev:
+ if set(e[:2]) == {1, 2}:
+ assert e[2] in {0, 3}
+ if e[2] == 3:
+ assert e[3] == "bar"
+ else: # e[2] == 0
+ assert e[3] == 1
+ if G.is_directed():
+ assert len(list(ev)) == 3
+ else:
+ assert len(list(ev)) == 4
+
+ def test_or(self):
+ # print("G | H edges:", gnv | hnv)
+ ev = self.eview(self.G)
+ some_edges = {(0, 1, 0), (1, 0, 0), (0, 2, 0)}
+ result = {(n, n + 1, 0) for n in range(8)}
+ result.update(some_edges)
+ result.update({(1, 2, 3)})
+ assert ev | some_edges == result
+ assert some_edges | ev == result
+
+ def test_sub(self):
+ # print("G - H edges:", gnv - hnv)
+ ev = self.eview(self.G)
+ some_edges = {(0, 1, 0), (1, 0, 0), (0, 2, 0)}
+ result = {(n, n + 1, 0) for n in range(8)}
+ result.remove((0, 1, 0))
+ result.update({(1, 2, 3)})
+ assert ev - some_edges, result
+ assert some_edges - ev, result
+
+ def test_xor(self):
+ # print("G ^ H edges:", gnv ^ hnv)
+ ev = self.eview(self.G)
+ some_edges = {(0, 1, 0), (1, 0, 0), (0, 2, 0)}
+ if self.G.is_directed():
+ result = {(n, n + 1, 0) for n in range(1, 8)}
+ result.update({(1, 0, 0), (0, 2, 0), (1, 2, 3)})
+ assert ev ^ some_edges == result
+ assert some_edges ^ ev == result
+ else:
+ result = {(n, n + 1, 0) for n in range(1, 8)}
+ result.update({(0, 2, 0), (1, 2, 3)})
+ assert ev ^ some_edges == result
+ assert some_edges ^ ev == result
+
+ def test_and(self):
+ # print("G & H edges:", gnv & hnv)
+ ev = self.eview(self.G)
+ some_edges = {(0, 1, 0), (1, 0, 0), (0, 2, 0)}
+ if self.G.is_directed():
+ assert ev & some_edges == {(0, 1, 0)}
+ assert some_edges & ev == {(0, 1, 0)}
+ else:
+ assert ev & some_edges == {(0, 1, 0), (1, 0, 0)}
+ assert some_edges & ev == {(0, 1, 0), (1, 0, 0)}
+
+ def test_contains_with_nbunch(self):
+ ev = self.eview(self.G)
+ evn = ev(nbunch=[0, 2])
+ assert (0, 1) in evn
+ assert (1, 2) in evn
+ assert (2, 3) in evn
+ assert (3, 4) not in evn
+ assert (4, 5) not in evn
+ assert (5, 6) not in evn
+ assert (7, 8) not in evn
+ assert (8, 9) not in evn
+
+
+class TestOutMultiEdgeView(TestMultiEdgeView):
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9, nx.MultiDiGraph())
+ cls.G.add_edge(1, 2, key=3, foo="bar")
+ cls.eview = nx.reportviews.OutMultiEdgeView
+
+ def modify_edge(self, G, e, **kwds):
+ if len(e) == 2:
+ e = e + (0,)
+ G._adj[e[0]][e[1]][e[2]].update(kwds)
+
+ def test_repr(self):
+ ev = self.eview(self.G)
+ rep = (
+ "OutMultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 3), (2, 3, 0),"
+ + " (3, 4, 0), (4, 5, 0), (5, 6, 0), (6, 7, 0), (7, 8, 0)])"
+ )
+ assert repr(ev) == rep
+
+ def test_contains_with_nbunch(self):
+ ev = self.eview(self.G)
+ evn = ev(nbunch=[0, 2])
+ assert (0, 1) in evn
+ assert (1, 2) not in evn
+ assert (2, 3) in evn
+ assert (3, 4) not in evn
+ assert (4, 5) not in evn
+ assert (5, 6) not in evn
+ assert (7, 8) not in evn
+ assert (8, 9) not in evn
+
+
+class TestInMultiEdgeView(TestMultiEdgeView):
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9, nx.MultiDiGraph())
+ cls.G.add_edge(1, 2, key=3, foo="bar")
+ cls.eview = nx.reportviews.InMultiEdgeView
+
+ def modify_edge(self, G, e, **kwds):
+ if len(e) == 2:
+ e = e + (0,)
+ G._adj[e[0]][e[1]][e[2]].update(kwds)
+
+ def test_repr(self):
+ ev = self.eview(self.G)
+ rep = (
+ "InMultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 3), (2, 3, 0), "
+ + "(3, 4, 0), (4, 5, 0), (5, 6, 0), (6, 7, 0), (7, 8, 0)])"
+ )
+ assert repr(ev) == rep
+
+ def test_contains_with_nbunch(self):
+ ev = self.eview(self.G)
+ evn = ev(nbunch=[0, 2])
+ assert (0, 1) not in evn
+ assert (1, 2) in evn
+ assert (2, 3) not in evn
+ assert (3, 4) not in evn
+ assert (4, 5) not in evn
+ assert (5, 6) not in evn
+ assert (7, 8) not in evn
+ assert (8, 9) not in evn
+
+
+# Degrees
+class TestDegreeView:
+ GRAPH = nx.Graph
+ dview = nx.reportviews.DegreeView
+
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(6, cls.GRAPH())
+ cls.G.add_edge(1, 3, foo=2)
+ cls.G.add_edge(1, 3, foo=3)
+
+ def test_pickle(self):
+ import pickle
+
+ deg = self.G.degree
+ pdeg = pickle.loads(pickle.dumps(deg, -1))
+ assert dict(deg) == dict(pdeg)
+
+ def test_str(self):
+ dv = self.dview(self.G)
+ rep = str([(0, 1), (1, 3), (2, 2), (3, 3), (4, 2), (5, 1)])
+ assert str(dv) == rep
+ dv = self.G.degree()
+ assert str(dv) == rep
+
+ def test_repr(self):
+ dv = self.dview(self.G)
+ rep = "DegreeView({0: 1, 1: 3, 2: 2, 3: 3, 4: 2, 5: 1})"
+ assert repr(dv) == rep
+
+ def test_iter(self):
+ dv = self.dview(self.G)
+ for n, d in dv:
+ pass
+ idv = iter(dv)
+ assert iter(dv) != dv
+ assert iter(idv) == idv
+ assert next(idv) == (0, dv[0])
+ assert next(idv) == (1, dv[1])
+ # weighted
+ dv = self.dview(self.G, weight="foo")
+ for n, d in dv:
+ pass
+ idv = iter(dv)
+ assert iter(dv) != dv
+ assert iter(idv) == idv
+ assert next(idv) == (0, dv[0])
+ assert next(idv) == (1, dv[1])
+
+ def test_nbunch(self):
+ dv = self.dview(self.G)
+ dvn = dv(0)
+ assert dvn == 1
+ dvn = dv([2, 3])
+ assert sorted(dvn) == [(2, 2), (3, 3)]
+
+ def test_getitem(self):
+ dv = self.dview(self.G)
+ assert dv[0] == 1
+ assert dv[1] == 3
+ assert dv[2] == 2
+ assert dv[3] == 3
+ dv = self.dview(self.G, weight="foo")
+ assert dv[0] == 1
+ assert dv[1] == 5
+ assert dv[2] == 2
+ assert dv[3] == 5
+
+ def test_weight(self):
+ dv = self.dview(self.G)
+ dvw = dv(0, weight="foo")
+ assert dvw == 1
+ dvw = dv(1, weight="foo")
+ assert dvw == 5
+ dvw = dv([2, 3], weight="foo")
+ assert sorted(dvw) == [(2, 2), (3, 5)]
+ dvd = dict(dv(weight="foo"))
+ assert dvd[0] == 1
+ assert dvd[1] == 5
+ assert dvd[2] == 2
+ assert dvd[3] == 5
+
+ def test_len(self):
+ dv = self.dview(self.G)
+ assert len(dv) == 6
+
+
+class TestDiDegreeView(TestDegreeView):
+ GRAPH = nx.DiGraph
+ dview = nx.reportviews.DiDegreeView
+
+ def test_repr(self):
+ dv = self.G.degree()
+ rep = "DiDegreeView({0: 1, 1: 3, 2: 2, 3: 3, 4: 2, 5: 1})"
+ assert repr(dv) == rep
+
+
+class TestOutDegreeView(TestDegreeView):
+ GRAPH = nx.DiGraph
+ dview = nx.reportviews.OutDegreeView
+
+ def test_str(self):
+ dv = self.dview(self.G)
+ rep = str([(0, 1), (1, 2), (2, 1), (3, 1), (4, 1), (5, 0)])
+ assert str(dv) == rep
+ dv = self.G.out_degree()
+ assert str(dv) == rep
+
+ def test_repr(self):
+ dv = self.G.out_degree()
+ rep = "OutDegreeView({0: 1, 1: 2, 2: 1, 3: 1, 4: 1, 5: 0})"
+ assert repr(dv) == rep
+
+ def test_nbunch(self):
+ dv = self.dview(self.G)
+ dvn = dv(0)
+ assert dvn == 1
+ dvn = dv([2, 3])
+ assert sorted(dvn) == [(2, 1), (3, 1)]
+
+ def test_getitem(self):
+ dv = self.dview(self.G)
+ assert dv[0] == 1
+ assert dv[1] == 2
+ assert dv[2] == 1
+ assert dv[3] == 1
+ dv = self.dview(self.G, weight="foo")
+ assert dv[0] == 1
+ assert dv[1] == 4
+ assert dv[2] == 1
+ assert dv[3] == 1
+
+ def test_weight(self):
+ dv = self.dview(self.G)
+ dvw = dv(0, weight="foo")
+ assert dvw == 1
+ dvw = dv(1, weight="foo")
+ assert dvw == 4
+ dvw = dv([2, 3], weight="foo")
+ assert sorted(dvw) == [(2, 1), (3, 1)]
+ dvd = dict(dv(weight="foo"))
+ assert dvd[0] == 1
+ assert dvd[1] == 4
+ assert dvd[2] == 1
+ assert dvd[3] == 1
+
+
+class TestInDegreeView(TestDegreeView):
+ GRAPH = nx.DiGraph
+ dview = nx.reportviews.InDegreeView
+
+ def test_str(self):
+ dv = self.dview(self.G)
+ rep = str([(0, 0), (1, 1), (2, 1), (3, 2), (4, 1), (5, 1)])
+ assert str(dv) == rep
+ dv = self.G.in_degree()
+ assert str(dv) == rep
+
+ def test_repr(self):
+ dv = self.G.in_degree()
+ rep = "InDegreeView({0: 0, 1: 1, 2: 1, 3: 2, 4: 1, 5: 1})"
+ assert repr(dv) == rep
+
+ def test_nbunch(self):
+ dv = self.dview(self.G)
+ dvn = dv(0)
+ assert dvn == 0
+ dvn = dv([2, 3])
+ assert sorted(dvn) == [(2, 1), (3, 2)]
+
+ def test_getitem(self):
+ dv = self.dview(self.G)
+ assert dv[0] == 0
+ assert dv[1] == 1
+ assert dv[2] == 1
+ assert dv[3] == 2
+ dv = self.dview(self.G, weight="foo")
+ assert dv[0] == 0
+ assert dv[1] == 1
+ assert dv[2] == 1
+ assert dv[3] == 4
+
+ def test_weight(self):
+ dv = self.dview(self.G)
+ dvw = dv(0, weight="foo")
+ assert dvw == 0
+ dvw = dv(1, weight="foo")
+ assert dvw == 1
+ dvw = dv([2, 3], weight="foo")
+ assert sorted(dvw) == [(2, 1), (3, 4)]
+ dvd = dict(dv(weight="foo"))
+ assert dvd[0] == 0
+ assert dvd[1] == 1
+ assert dvd[2] == 1
+ assert dvd[3] == 4
+
+
+class TestMultiDegreeView(TestDegreeView):
+ GRAPH = nx.MultiGraph
+ dview = nx.reportviews.MultiDegreeView
+
+ def test_str(self):
+ dv = self.dview(self.G)
+ rep = str([(0, 1), (1, 4), (2, 2), (3, 4), (4, 2), (5, 1)])
+ assert str(dv) == rep
+ dv = self.G.degree()
+ assert str(dv) == rep
+
+ def test_repr(self):
+ dv = self.G.degree()
+ rep = "MultiDegreeView({0: 1, 1: 4, 2: 2, 3: 4, 4: 2, 5: 1})"
+ assert repr(dv) == rep
+
+ def test_nbunch(self):
+ dv = self.dview(self.G)
+ dvn = dv(0)
+ assert dvn == 1
+ dvn = dv([2, 3])
+ assert sorted(dvn) == [(2, 2), (3, 4)]
+
+ def test_getitem(self):
+ dv = self.dview(self.G)
+ assert dv[0] == 1
+ assert dv[1] == 4
+ assert dv[2] == 2
+ assert dv[3] == 4
+ dv = self.dview(self.G, weight="foo")
+ assert dv[0] == 1
+ assert dv[1] == 7
+ assert dv[2] == 2
+ assert dv[3] == 7
+
+ def test_weight(self):
+ dv = self.dview(self.G)
+ dvw = dv(0, weight="foo")
+ assert dvw == 1
+ dvw = dv(1, weight="foo")
+ assert dvw == 7
+ dvw = dv([2, 3], weight="foo")
+ assert sorted(dvw) == [(2, 2), (3, 7)]
+ dvd = dict(dv(weight="foo"))
+ assert dvd[0] == 1
+ assert dvd[1] == 7
+ assert dvd[2] == 2
+ assert dvd[3] == 7
+
+
+class TestDiMultiDegreeView(TestMultiDegreeView):
+ GRAPH = nx.MultiDiGraph
+ dview = nx.reportviews.DiMultiDegreeView
+
+ def test_repr(self):
+ dv = self.G.degree()
+ rep = "DiMultiDegreeView({0: 1, 1: 4, 2: 2, 3: 4, 4: 2, 5: 1})"
+ assert repr(dv) == rep
+
+
+class TestOutMultiDegreeView(TestDegreeView):
+ GRAPH = nx.MultiDiGraph
+ dview = nx.reportviews.OutMultiDegreeView
+
+ def test_str(self):
+ dv = self.dview(self.G)
+ rep = str([(0, 1), (1, 3), (2, 1), (3, 1), (4, 1), (5, 0)])
+ assert str(dv) == rep
+ dv = self.G.out_degree()
+ assert str(dv) == rep
+
+ def test_repr(self):
+ dv = self.G.out_degree()
+ rep = "OutMultiDegreeView({0: 1, 1: 3, 2: 1, 3: 1, 4: 1, 5: 0})"
+ assert repr(dv) == rep
+
+ def test_nbunch(self):
+ dv = self.dview(self.G)
+ dvn = dv(0)
+ assert dvn == 1
+ dvn = dv([2, 3])
+ assert sorted(dvn) == [(2, 1), (3, 1)]
+
+ def test_getitem(self):
+ dv = self.dview(self.G)
+ assert dv[0] == 1
+ assert dv[1] == 3
+ assert dv[2] == 1
+ assert dv[3] == 1
+ dv = self.dview(self.G, weight="foo")
+ assert dv[0] == 1
+ assert dv[1] == 6
+ assert dv[2] == 1
+ assert dv[3] == 1
+
+ def test_weight(self):
+ dv = self.dview(self.G)
+ dvw = dv(0, weight="foo")
+ assert dvw == 1
+ dvw = dv(1, weight="foo")
+ assert dvw == 6
+ dvw = dv([2, 3], weight="foo")
+ assert sorted(dvw) == [(2, 1), (3, 1)]
+ dvd = dict(dv(weight="foo"))
+ assert dvd[0] == 1
+ assert dvd[1] == 6
+ assert dvd[2] == 1
+ assert dvd[3] == 1
+
+
+class TestInMultiDegreeView(TestDegreeView):
+ GRAPH = nx.MultiDiGraph
+ dview = nx.reportviews.InMultiDegreeView
+
+ def test_str(self):
+ dv = self.dview(self.G)
+ rep = str([(0, 0), (1, 1), (2, 1), (3, 3), (4, 1), (5, 1)])
+ assert str(dv) == rep
+ dv = self.G.in_degree()
+ assert str(dv) == rep
+
+ def test_repr(self):
+ dv = self.G.in_degree()
+ rep = "InMultiDegreeView({0: 0, 1: 1, 2: 1, 3: 3, 4: 1, 5: 1})"
+ assert repr(dv) == rep
+
+ def test_nbunch(self):
+ dv = self.dview(self.G)
+ dvn = dv(0)
+ assert dvn == 0
+ dvn = dv([2, 3])
+ assert sorted(dvn) == [(2, 1), (3, 3)]
+
+ def test_getitem(self):
+ dv = self.dview(self.G)
+ assert dv[0] == 0
+ assert dv[1] == 1
+ assert dv[2] == 1
+ assert dv[3] == 3
+ dv = self.dview(self.G, weight="foo")
+ assert dv[0] == 0
+ assert dv[1] == 1
+ assert dv[2] == 1
+ assert dv[3] == 6
+
+ def test_weight(self):
+ dv = self.dview(self.G)
+ dvw = dv(0, weight="foo")
+ assert dvw == 0
+ dvw = dv(1, weight="foo")
+ assert dvw == 1
+ dvw = dv([2, 3], weight="foo")
+ assert sorted(dvw) == [(2, 1), (3, 6)]
+ dvd = dict(dv(weight="foo"))
+ assert dvd[0] == 0
+ assert dvd[1] == 1
+ assert dvd[2] == 1
+ assert dvd[3] == 6
+
+
+@pytest.mark.parametrize(
+ ("reportview", "err_msg_terms"),
+ (
+ (rv.NodeView, "list(G.nodes"),
+ (rv.NodeDataView, "list(G.nodes.data"),
+ (rv.EdgeView, "list(G.edges"),
+ # Directed EdgeViews
+ (rv.InEdgeView, "list(G.in_edges"),
+ (rv.OutEdgeView, "list(G.edges"),
+ # Multi EdgeViews
+ (rv.MultiEdgeView, "list(G.edges"),
+ (rv.InMultiEdgeView, "list(G.in_edges"),
+ (rv.OutMultiEdgeView, "list(G.edges"),
+ ),
+)
+def test_slicing_reportviews(reportview, err_msg_terms):
+ G = nx.complete_graph(3)
+ view = reportview(G)
+ with pytest.raises(nx.NetworkXError) as exc:
+ view[0:2]
+ errmsg = str(exc.value)
+ assert type(view).__name__ in errmsg
+ assert err_msg_terms in errmsg
+
+
+@pytest.mark.parametrize(
+ "graph", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
+)
+def test_cache_dict_get_set_state(graph):
+ G = nx.path_graph(5, graph())
+ G.nodes, G.edges, G.adj, G.degree
+ if G.is_directed():
+ G.pred, G.succ, G.in_edges, G.out_edges, G.in_degree, G.out_degree
+ cached_dict = G.__dict__
+ assert "nodes" in cached_dict
+ assert "edges" in cached_dict
+ assert "adj" in cached_dict
+ assert "degree" in cached_dict
+ if G.is_directed():
+ assert "pred" in cached_dict
+ assert "succ" in cached_dict
+ assert "in_edges" in cached_dict
+ assert "out_edges" in cached_dict
+ assert "in_degree" in cached_dict
+ assert "out_degree" in cached_dict
+
+ # Raises error if the cached properties and views do not work
+ pickle.loads(pickle.dumps(G, -1))
+ deepcopy(G)
+
+
+def test_edge_views_inherit_from_EdgeViewABC():
+ all_edge_view_classes = (v for v in dir(nx.reportviews) if "Edge" in v)
+ for eview_class in all_edge_view_classes:
+ assert issubclass(
+ getattr(nx.reportviews, eview_class), nx.reportviews.EdgeViewABC
+ )
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_special.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_special.py
new file mode 100644
index 00000000..1fa79605
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_special.py
@@ -0,0 +1,131 @@
+import networkx as nx
+
+from .test_digraph import BaseDiGraphTester
+from .test_digraph import TestDiGraph as _TestDiGraph
+from .test_graph import BaseGraphTester
+from .test_graph import TestGraph as _TestGraph
+from .test_multidigraph import TestMultiDiGraph as _TestMultiDiGraph
+from .test_multigraph import TestMultiGraph as _TestMultiGraph
+
+
+def test_factories():
+ class mydict1(dict):
+ pass
+
+ class mydict2(dict):
+ pass
+
+ class mydict3(dict):
+ pass
+
+ class mydict4(dict):
+ pass
+
+ class mydict5(dict):
+ pass
+
+ for Graph in (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph):
+ # print("testing class: ", Graph.__name__)
+ class MyGraph(Graph):
+ node_dict_factory = mydict1
+ adjlist_outer_dict_factory = mydict2
+ adjlist_inner_dict_factory = mydict3
+ edge_key_dict_factory = mydict4
+ edge_attr_dict_factory = mydict5
+
+ G = MyGraph()
+ assert isinstance(G._node, mydict1)
+ assert isinstance(G._adj, mydict2)
+ G.add_node(1)
+ assert isinstance(G._adj[1], mydict3)
+ if G.is_directed():
+ assert isinstance(G._pred, mydict2)
+ assert isinstance(G._succ, mydict2)
+ assert isinstance(G._pred[1], mydict3)
+ G.add_edge(1, 2)
+ if G.is_multigraph():
+ assert isinstance(G._adj[1][2], mydict4)
+ assert isinstance(G._adj[1][2][0], mydict5)
+ else:
+ assert isinstance(G._adj[1][2], mydict5)
+
+
+class TestSpecialGraph(_TestGraph):
+ def setup_method(self):
+ _TestGraph.setup_method(self)
+ self.Graph = nx.Graph
+
+
+class TestThinGraph(BaseGraphTester):
+ def setup_method(self):
+ all_edge_dict = {"weight": 1}
+
+ class MyGraph(nx.Graph):
+ def edge_attr_dict_factory(self):
+ return all_edge_dict
+
+ self.Graph = MyGraph
+ # build dict-of-dict-of-dict K3
+ ed1, ed2, ed3 = (all_edge_dict, all_edge_dict, all_edge_dict)
+ self.k3adj = {0: {1: ed1, 2: ed2}, 1: {0: ed1, 2: ed3}, 2: {0: ed2, 1: ed3}}
+ self.k3edges = [(0, 1), (0, 2), (1, 2)]
+ self.k3nodes = [0, 1, 2]
+ self.K3 = self.Graph()
+ self.K3._adj = self.k3adj
+ self.K3._node = {}
+ self.K3._node[0] = {}
+ self.K3._node[1] = {}
+ self.K3._node[2] = {}
+
+
+class TestSpecialDiGraph(_TestDiGraph):
+ def setup_method(self):
+ _TestDiGraph.setup_method(self)
+ self.Graph = nx.DiGraph
+
+
+class TestThinDiGraph(BaseDiGraphTester):
+ def setup_method(self):
+ all_edge_dict = {"weight": 1}
+
+ class MyGraph(nx.DiGraph):
+ def edge_attr_dict_factory(self):
+ return all_edge_dict
+
+ self.Graph = MyGraph
+ # build dict-of-dict-of-dict K3
+ ed1, ed2, ed3 = (all_edge_dict, all_edge_dict, all_edge_dict)
+ ed4, ed5, ed6 = (all_edge_dict, all_edge_dict, all_edge_dict)
+ self.k3adj = {0: {1: ed1, 2: ed2}, 1: {0: ed3, 2: ed4}, 2: {0: ed5, 1: ed6}}
+ self.k3edges = [(0, 1), (0, 2), (1, 2)]
+ self.k3nodes = [0, 1, 2]
+ self.K3 = self.Graph()
+ self.K3._succ = self.k3adj
+ # K3._adj is synced with K3._succ
+ self.K3._pred = {0: {1: ed3, 2: ed5}, 1: {0: ed1, 2: ed6}, 2: {0: ed2, 1: ed4}}
+ self.K3._node = {}
+ self.K3._node[0] = {}
+ self.K3._node[1] = {}
+ self.K3._node[2] = {}
+
+ ed1, ed2 = (all_edge_dict, all_edge_dict)
+ self.P3 = self.Graph()
+ self.P3._succ = {0: {1: ed1}, 1: {2: ed2}, 2: {}}
+ # P3._adj is synced with P3._succ
+ self.P3._pred = {0: {}, 1: {0: ed1}, 2: {1: ed2}}
+ self.P3._node = {}
+ self.P3._node[0] = {}
+ self.P3._node[1] = {}
+ self.P3._node[2] = {}
+
+
+class TestSpecialMultiGraph(_TestMultiGraph):
+ def setup_method(self):
+ _TestMultiGraph.setup_method(self)
+ self.Graph = nx.MultiGraph
+
+
+class TestSpecialMultiDiGraph(_TestMultiDiGraph):
+ def setup_method(self):
+ _TestMultiDiGraph.setup_method(self)
+ self.Graph = nx.MultiDiGraph
diff --git a/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_subgraphviews.py b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_subgraphviews.py
new file mode 100644
index 00000000..73e0fdd2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/classes/tests/test_subgraphviews.py
@@ -0,0 +1,362 @@
+import pytest
+
+import networkx as nx
+from networkx.utils import edges_equal
+
+
+class TestSubGraphView:
+ gview = staticmethod(nx.subgraph_view)
+ graph = nx.Graph
+ hide_edges_filter = staticmethod(nx.filters.hide_edges)
+ show_edges_filter = staticmethod(nx.filters.show_edges)
+
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9, create_using=cls.graph())
+ cls.hide_edges_w_hide_nodes = {(3, 4), (4, 5), (5, 6)}
+
+ def test_hidden_nodes(self):
+ hide_nodes = [4, 5, 111]
+ nodes_gone = nx.filters.hide_nodes(hide_nodes)
+ gview = self.gview
+ G = gview(self.G, filter_node=nodes_gone)
+ assert self.G.nodes - G.nodes == {4, 5}
+ assert self.G.edges - G.edges == self.hide_edges_w_hide_nodes
+ if G.is_directed():
+ assert list(G[3]) == []
+ assert list(G[2]) == [3]
+ else:
+ assert list(G[3]) == [2]
+ assert set(G[2]) == {1, 3}
+ pytest.raises(KeyError, G.__getitem__, 4)
+ pytest.raises(KeyError, G.__getitem__, 112)
+ pytest.raises(KeyError, G.__getitem__, 111)
+ assert G.degree(3) == (3 if G.is_multigraph() else 1)
+ assert G.size() == (7 if G.is_multigraph() else 5)
+
+ def test_hidden_edges(self):
+ hide_edges = [(2, 3), (8, 7), (222, 223)]
+ edges_gone = self.hide_edges_filter(hide_edges)
+ gview = self.gview
+ G = gview(self.G, filter_edge=edges_gone)
+ assert self.G.nodes == G.nodes
+ if G.is_directed():
+ assert self.G.edges - G.edges == {(2, 3)}
+ assert list(G[2]) == []
+ assert list(G.pred[3]) == []
+ assert list(G.pred[2]) == [1]
+ assert G.size() == 7
+ else:
+ assert self.G.edges - G.edges == {(2, 3), (7, 8)}
+ assert list(G[2]) == [1]
+ assert G.size() == 6
+ assert list(G[3]) == [4]
+ pytest.raises(KeyError, G.__getitem__, 221)
+ pytest.raises(KeyError, G.__getitem__, 222)
+ assert G.degree(3) == 1
+
+ def test_shown_node(self):
+ induced_subgraph = nx.filters.show_nodes([2, 3, 111])
+ gview = self.gview
+ G = gview(self.G, filter_node=induced_subgraph)
+ assert set(G.nodes) == {2, 3}
+ if G.is_directed():
+ assert list(G[3]) == []
+ else:
+ assert list(G[3]) == [2]
+ assert list(G[2]) == [3]
+ pytest.raises(KeyError, G.__getitem__, 4)
+ pytest.raises(KeyError, G.__getitem__, 112)
+ pytest.raises(KeyError, G.__getitem__, 111)
+ assert G.degree(3) == (3 if G.is_multigraph() else 1)
+ assert G.size() == (3 if G.is_multigraph() else 1)
+
+ def test_shown_edges(self):
+ show_edges = [(2, 3), (8, 7), (222, 223)]
+ edge_subgraph = self.show_edges_filter(show_edges)
+ G = self.gview(self.G, filter_edge=edge_subgraph)
+ assert self.G.nodes == G.nodes
+ if G.is_directed():
+ assert G.edges == {(2, 3)}
+ assert list(G[3]) == []
+ assert list(G[2]) == [3]
+ assert list(G.pred[3]) == [2]
+ assert list(G.pred[2]) == []
+ assert G.size() == 1
+ else:
+ assert G.edges == {(2, 3), (7, 8)}
+ assert list(G[3]) == [2]
+ assert list(G[2]) == [3]
+ assert G.size() == 2
+ pytest.raises(KeyError, G.__getitem__, 221)
+ pytest.raises(KeyError, G.__getitem__, 222)
+ assert G.degree(3) == 1
+
+
+class TestSubDiGraphView(TestSubGraphView):
+ gview = staticmethod(nx.subgraph_view)
+ graph = nx.DiGraph
+ hide_edges_filter = staticmethod(nx.filters.hide_diedges)
+ show_edges_filter = staticmethod(nx.filters.show_diedges)
+ hide_edges = [(2, 3), (8, 7), (222, 223)]
+ excluded = {(2, 3), (3, 4), (4, 5), (5, 6)}
+
+ def test_inoutedges(self):
+ edges_gone = self.hide_edges_filter(self.hide_edges)
+ hide_nodes = [4, 5, 111]
+ nodes_gone = nx.filters.hide_nodes(hide_nodes)
+ G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
+
+ assert self.G.in_edges - G.in_edges == self.excluded
+ assert self.G.out_edges - G.out_edges == self.excluded
+
+ def test_pred(self):
+ edges_gone = self.hide_edges_filter(self.hide_edges)
+ hide_nodes = [4, 5, 111]
+ nodes_gone = nx.filters.hide_nodes(hide_nodes)
+ G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
+
+ assert list(G.pred[2]) == [1]
+ assert list(G.pred[6]) == []
+
+ def test_inout_degree(self):
+ edges_gone = self.hide_edges_filter(self.hide_edges)
+ hide_nodes = [4, 5, 111]
+ nodes_gone = nx.filters.hide_nodes(hide_nodes)
+ G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
+
+ assert G.degree(2) == 1
+ assert G.out_degree(2) == 0
+ assert G.in_degree(2) == 1
+ assert G.size() == 4
+
+
+# multigraph
+class TestMultiGraphView(TestSubGraphView):
+ gview = staticmethod(nx.subgraph_view)
+ graph = nx.MultiGraph
+ hide_edges_filter = staticmethod(nx.filters.hide_multiedges)
+ show_edges_filter = staticmethod(nx.filters.show_multiedges)
+
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.path_graph(9, create_using=cls.graph())
+ multiedges = {(2, 3, 4), (2, 3, 5)}
+ cls.G.add_edges_from(multiedges)
+ cls.hide_edges_w_hide_nodes = {(3, 4, 0), (4, 5, 0), (5, 6, 0)}
+
+ def test_hidden_edges(self):
+ hide_edges = [(2, 3, 4), (2, 3, 3), (8, 7, 0), (222, 223, 0)]
+ edges_gone = self.hide_edges_filter(hide_edges)
+ G = self.gview(self.G, filter_edge=edges_gone)
+ assert self.G.nodes == G.nodes
+ if G.is_directed():
+ assert self.G.edges - G.edges == {(2, 3, 4)}
+ assert list(G[3]) == [4]
+ assert list(G[2]) == [3]
+ assert list(G.pred[3]) == [2] # only one 2 but two edges
+ assert list(G.pred[2]) == [1]
+ assert G.size() == 9
+ else:
+ assert self.G.edges - G.edges == {(2, 3, 4), (7, 8, 0)}
+ assert list(G[3]) == [2, 4]
+ assert list(G[2]) == [1, 3]
+ assert G.size() == 8
+ assert G.degree(3) == 3
+ pytest.raises(KeyError, G.__getitem__, 221)
+ pytest.raises(KeyError, G.__getitem__, 222)
+
+ def test_shown_edges(self):
+ show_edges = [(2, 3, 4), (2, 3, 3), (8, 7, 0), (222, 223, 0)]
+ edge_subgraph = self.show_edges_filter(show_edges)
+ G = self.gview(self.G, filter_edge=edge_subgraph)
+ assert self.G.nodes == G.nodes
+ if G.is_directed():
+ assert G.edges == {(2, 3, 4)}
+ assert list(G[3]) == []
+ assert list(G.pred[3]) == [2]
+ assert list(G.pred[2]) == []
+ assert G.size() == 1
+ else:
+ assert G.edges == {(2, 3, 4), (7, 8, 0)}
+ assert G.size() == 2
+ assert list(G[3]) == [2]
+ assert G.degree(3) == 1
+ assert list(G[2]) == [3]
+ pytest.raises(KeyError, G.__getitem__, 221)
+ pytest.raises(KeyError, G.__getitem__, 222)
+
+
+# multidigraph
+class TestMultiDiGraphView(TestMultiGraphView, TestSubDiGraphView):
+ gview = staticmethod(nx.subgraph_view)
+ graph = nx.MultiDiGraph
+ hide_edges_filter = staticmethod(nx.filters.hide_multidiedges)
+ show_edges_filter = staticmethod(nx.filters.show_multidiedges)
+ hide_edges = [(2, 3, 0), (8, 7, 0), (222, 223, 0)]
+ excluded = {(2, 3, 0), (3, 4, 0), (4, 5, 0), (5, 6, 0)}
+
+ def test_inout_degree(self):
+ edges_gone = self.hide_edges_filter(self.hide_edges)
+ hide_nodes = [4, 5, 111]
+ nodes_gone = nx.filters.hide_nodes(hide_nodes)
+ G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
+
+ assert G.degree(2) == 3
+ assert G.out_degree(2) == 2
+ assert G.in_degree(2) == 1
+ assert G.size() == 6
+
+
+# induced_subgraph
+class TestInducedSubGraph:
+ @classmethod
+ def setup_class(cls):
+ cls.K3 = G = nx.complete_graph(3)
+ G.graph["foo"] = []
+ G.nodes[0]["foo"] = []
+ G.remove_edge(1, 2)
+ ll = []
+ G.add_edge(1, 2, foo=ll)
+ G.add_edge(2, 1, foo=ll)
+
+ def test_full_graph(self):
+ G = self.K3
+ H = nx.induced_subgraph(G, [0, 1, 2, 5])
+ assert H.name == G.name
+ self.graphs_equal(H, G)
+ self.same_attrdict(H, G)
+
+ def test_partial_subgraph(self):
+ G = self.K3
+ H = nx.induced_subgraph(G, 0)
+ assert dict(H.adj) == {0: {}}
+ assert dict(G.adj) != {0: {}}
+
+ H = nx.induced_subgraph(G, [0, 1])
+ assert dict(H.adj) == {0: {1: {}}, 1: {0: {}}}
+
+ def same_attrdict(self, H, G):
+ old_foo = H[1][2]["foo"]
+ H.edges[1, 2]["foo"] = "baz"
+ assert G.edges == H.edges
+ H.edges[1, 2]["foo"] = old_foo
+ assert G.edges == H.edges
+ old_foo = H.nodes[0]["foo"]
+ H.nodes[0]["foo"] = "baz"
+ assert G.nodes == H.nodes
+ H.nodes[0]["foo"] = old_foo
+ assert G.nodes == H.nodes
+
+ def graphs_equal(self, H, G):
+ assert G._adj == H._adj
+ assert G._node == H._node
+ assert G.graph == H.graph
+ assert G.name == H.name
+ if not G.is_directed() and not H.is_directed():
+ assert H._adj[1][2] is H._adj[2][1]
+ assert G._adj[1][2] is G._adj[2][1]
+ else: # at least one is directed
+ if not G.is_directed():
+ G._pred = G._adj
+ G._succ = G._adj
+ if not H.is_directed():
+ H._pred = H._adj
+ H._succ = H._adj
+ assert G._pred == H._pred
+ assert G._succ == H._succ
+ assert H._succ[1][2] is H._pred[2][1]
+ assert G._succ[1][2] is G._pred[2][1]
+
+
+# edge_subgraph
+class TestEdgeSubGraph:
+ @classmethod
+ def setup_class(cls):
+ # Create a path graph on five nodes.
+ cls.G = G = nx.path_graph(5)
+ # Add some node, edge, and graph attributes.
+ for i in range(5):
+ G.nodes[i]["name"] = f"node{i}"
+ G.edges[0, 1]["name"] = "edge01"
+ G.edges[3, 4]["name"] = "edge34"
+ G.graph["name"] = "graph"
+ # Get the subgraph induced by the first and last edges.
+ cls.H = nx.edge_subgraph(G, [(0, 1), (3, 4)])
+
+ def test_correct_nodes(self):
+ """Tests that the subgraph has the correct nodes."""
+ assert [(0, "node0"), (1, "node1"), (3, "node3"), (4, "node4")] == sorted(
+ self.H.nodes.data("name")
+ )
+
+ def test_correct_edges(self):
+ """Tests that the subgraph has the correct edges."""
+ assert edges_equal(
+ [(0, 1, "edge01"), (3, 4, "edge34")], self.H.edges.data("name")
+ )
+
+ def test_add_node(self):
+ """Tests that adding a node to the original graph does not
+ affect the nodes of the subgraph.
+
+ """
+ self.G.add_node(5)
+ assert [0, 1, 3, 4] == sorted(self.H.nodes)
+ self.G.remove_node(5)
+
+ def test_remove_node(self):
+ """Tests that removing a node in the original graph
+ removes the nodes of the subgraph.
+
+ """
+ self.G.remove_node(0)
+ assert [1, 3, 4] == sorted(self.H.nodes)
+ self.G.add_node(0, name="node0")
+ self.G.add_edge(0, 1, name="edge01")
+
+ def test_node_attr_dict(self):
+ """Tests that the node attribute dictionary of the two graphs is
+ the same object.
+
+ """
+ for v in self.H:
+ assert self.G.nodes[v] == self.H.nodes[v]
+ # Making a change to G should make a change in H and vice versa.
+ self.G.nodes[0]["name"] = "foo"
+ assert self.G.nodes[0] == self.H.nodes[0]
+ self.H.nodes[1]["name"] = "bar"
+ assert self.G.nodes[1] == self.H.nodes[1]
+ # Revert the change, so tests pass with pytest-randomly
+ self.G.nodes[0]["name"] = "node0"
+ self.H.nodes[1]["name"] = "node1"
+
+ def test_edge_attr_dict(self):
+ """Tests that the edge attribute dictionary of the two graphs is
+ the same object.
+
+ """
+ for u, v in self.H.edges():
+ assert self.G.edges[u, v] == self.H.edges[u, v]
+ # Making a change to G should make a change in H and vice versa.
+ self.G.edges[0, 1]["name"] = "foo"
+ assert self.G.edges[0, 1]["name"] == self.H.edges[0, 1]["name"]
+ self.H.edges[3, 4]["name"] = "bar"
+ assert self.G.edges[3, 4]["name"] == self.H.edges[3, 4]["name"]
+ # Revert the change, so tests pass with pytest-randomly
+ self.G.edges[0, 1]["name"] = "edge01"
+ self.H.edges[3, 4]["name"] = "edge34"
+
+ def test_graph_attr_dict(self):
+ """Tests that the graph attribute dictionary of the two graphs
+ is the same object.
+
+ """
+ assert self.G.graph is self.H.graph
+
+ def test_readonly(self):
+ """Tests that the subgraph cannot change the graph structure"""
+ pytest.raises(nx.NetworkXError, self.H.add_node, 5)
+ pytest.raises(nx.NetworkXError, self.H.remove_node, 0)
+ pytest.raises(nx.NetworkXError, self.H.add_edge, 5, 6)
+ pytest.raises(nx.NetworkXError, self.H.remove_edge, 0, 1)