about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_all.py328
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_binary.py453
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_product.py491
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_unary.py55
5 files changed, 1327 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__init__.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_all.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_all.py
new file mode 100644
index 00000000..8ec29c15
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_all.py
@@ -0,0 +1,328 @@
+import pytest
+
+import networkx as nx
+from networkx.utils import edges_equal
+
+
+def test_union_all_attributes():
+    g = nx.Graph()
+    g.add_node(0, x=4)
+    g.add_node(1, x=5)
+    g.add_edge(0, 1, size=5)
+    g.graph["name"] = "g"
+
+    h = g.copy()
+    h.graph["name"] = "h"
+    h.graph["attr"] = "attr"
+    h.nodes[0]["x"] = 7
+
+    j = g.copy()
+    j.graph["name"] = "j"
+    j.graph["attr"] = "attr"
+    j.nodes[0]["x"] = 7
+
+    ghj = nx.union_all([g, h, j], rename=("g", "h", "j"))
+    assert set(ghj.nodes()) == {"h0", "h1", "g0", "g1", "j0", "j1"}
+    for n in ghj:
+        graph, node = n
+        assert ghj.nodes[n] == eval(graph).nodes[int(node)]
+
+    assert ghj.graph["attr"] == "attr"
+    assert ghj.graph["name"] == "j"  # j graph attributes take precedent
+
+
+def test_intersection_all():
+    G = nx.Graph()
+    H = nx.Graph()
+    R = nx.Graph(awesome=True)
+    G.add_nodes_from([1, 2, 3, 4])
+    G.add_edge(1, 2)
+    G.add_edge(2, 3)
+    H.add_nodes_from([1, 2, 3, 4])
+    H.add_edge(2, 3)
+    H.add_edge(3, 4)
+    R.add_nodes_from([1, 2, 3, 4])
+    R.add_edge(2, 3)
+    R.add_edge(4, 1)
+    I = nx.intersection_all([G, H, R])
+    assert set(I.nodes()) == {1, 2, 3, 4}
+    assert sorted(I.edges()) == [(2, 3)]
+    assert I.graph == {}
+
+
+def test_intersection_all_different_node_sets():
+    G = nx.Graph()
+    H = nx.Graph()
+    R = nx.Graph()
+    G.add_nodes_from([1, 2, 3, 4, 6, 7])
+    G.add_edge(1, 2)
+    G.add_edge(2, 3)
+    G.add_edge(6, 7)
+    H.add_nodes_from([1, 2, 3, 4])
+    H.add_edge(2, 3)
+    H.add_edge(3, 4)
+    R.add_nodes_from([1, 2, 3, 4, 8, 9])
+    R.add_edge(2, 3)
+    R.add_edge(4, 1)
+    R.add_edge(8, 9)
+    I = nx.intersection_all([G, H, R])
+    assert set(I.nodes()) == {1, 2, 3, 4}
+    assert sorted(I.edges()) == [(2, 3)]
+
+
+def test_intersection_all_attributes():
+    g = nx.Graph()
+    g.add_node(0, x=4)
+    g.add_node(1, x=5)
+    g.add_edge(0, 1, size=5)
+    g.graph["name"] = "g"
+
+    h = g.copy()
+    h.graph["name"] = "h"
+    h.graph["attr"] = "attr"
+    h.nodes[0]["x"] = 7
+
+    gh = nx.intersection_all([g, h])
+    assert set(gh.nodes()) == set(g.nodes())
+    assert set(gh.nodes()) == set(h.nodes())
+    assert sorted(gh.edges()) == sorted(g.edges())
+
+
+def test_intersection_all_attributes_different_node_sets():
+    g = nx.Graph()
+    g.add_node(0, x=4)
+    g.add_node(1, x=5)
+    g.add_edge(0, 1, size=5)
+    g.graph["name"] = "g"
+
+    h = g.copy()
+    g.add_node(2)
+    h.graph["name"] = "h"
+    h.graph["attr"] = "attr"
+    h.nodes[0]["x"] = 7
+
+    gh = nx.intersection_all([g, h])
+    assert set(gh.nodes()) == set(h.nodes())
+    assert sorted(gh.edges()) == sorted(g.edges())
+
+
+def test_intersection_all_multigraph_attributes():
+    g = nx.MultiGraph()
+    g.add_edge(0, 1, key=0)
+    g.add_edge(0, 1, key=1)
+    g.add_edge(0, 1, key=2)
+    h = nx.MultiGraph()
+    h.add_edge(0, 1, key=0)
+    h.add_edge(0, 1, key=3)
+    gh = nx.intersection_all([g, h])
+    assert set(gh.nodes()) == set(g.nodes())
+    assert set(gh.nodes()) == set(h.nodes())
+    assert sorted(gh.edges()) == [(0, 1)]
+    assert sorted(gh.edges(keys=True)) == [(0, 1, 0)]
+
+
+def test_intersection_all_multigraph_attributes_different_node_sets():
+    g = nx.MultiGraph()
+    g.add_edge(0, 1, key=0)
+    g.add_edge(0, 1, key=1)
+    g.add_edge(0, 1, key=2)
+    g.add_edge(1, 2, key=1)
+    g.add_edge(1, 2, key=2)
+    h = nx.MultiGraph()
+    h.add_edge(0, 1, key=0)
+    h.add_edge(0, 1, key=2)
+    h.add_edge(0, 1, key=3)
+    gh = nx.intersection_all([g, h])
+    assert set(gh.nodes()) == set(h.nodes())
+    assert sorted(gh.edges()) == [(0, 1), (0, 1)]
+    assert sorted(gh.edges(keys=True)) == [(0, 1, 0), (0, 1, 2)]
+
+
+def test_intersection_all_digraph():
+    g = nx.DiGraph()
+    g.add_edges_from([(1, 2), (2, 3)])
+    h = nx.DiGraph()
+    h.add_edges_from([(2, 1), (2, 3)])
+    gh = nx.intersection_all([g, h])
+    assert sorted(gh.edges()) == [(2, 3)]
+
+
+def test_union_all_and_compose_all():
+    K3 = nx.complete_graph(3)
+    P3 = nx.path_graph(3)
+
+    G1 = nx.DiGraph()
+    G1.add_edge("A", "B")
+    G1.add_edge("A", "C")
+    G1.add_edge("A", "D")
+    G2 = nx.DiGraph()
+    G2.add_edge("1", "2")
+    G2.add_edge("1", "3")
+    G2.add_edge("1", "4")
+
+    G = nx.union_all([G1, G2])
+    H = nx.compose_all([G1, G2])
+    assert edges_equal(G.edges(), H.edges())
+    assert not G.has_edge("A", "1")
+    pytest.raises(nx.NetworkXError, nx.union, K3, P3)
+    H1 = nx.union_all([H, G1], rename=("H", "G1"))
+    assert sorted(H1.nodes()) == [
+        "G1A",
+        "G1B",
+        "G1C",
+        "G1D",
+        "H1",
+        "H2",
+        "H3",
+        "H4",
+        "HA",
+        "HB",
+        "HC",
+        "HD",
+    ]
+
+    H2 = nx.union_all([H, G2], rename=("H", ""))
+    assert sorted(H2.nodes()) == [
+        "1",
+        "2",
+        "3",
+        "4",
+        "H1",
+        "H2",
+        "H3",
+        "H4",
+        "HA",
+        "HB",
+        "HC",
+        "HD",
+    ]
+
+    assert not H1.has_edge("NB", "NA")
+
+    G = nx.compose_all([G, G])
+    assert edges_equal(G.edges(), H.edges())
+
+    G2 = nx.union_all([G2, G2], rename=("", "copy"))
+    assert sorted(G2.nodes()) == [
+        "1",
+        "2",
+        "3",
+        "4",
+        "copy1",
+        "copy2",
+        "copy3",
+        "copy4",
+    ]
+
+    assert sorted(G2.neighbors("copy4")) == []
+    assert sorted(G2.neighbors("copy1")) == ["copy2", "copy3", "copy4"]
+    assert len(G) == 8
+    assert nx.number_of_edges(G) == 6
+
+    E = nx.disjoint_union_all([G, G])
+    assert len(E) == 16
+    assert nx.number_of_edges(E) == 12
+
+    E = nx.disjoint_union_all([G1, G2])
+    assert sorted(E.nodes()) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
+
+    G1 = nx.DiGraph()
+    G1.add_edge("A", "B")
+    G2 = nx.DiGraph()
+    G2.add_edge(1, 2)
+    G3 = nx.DiGraph()
+    G3.add_edge(11, 22)
+    G4 = nx.union_all([G1, G2, G3], rename=("G1", "G2", "G3"))
+    assert sorted(G4.nodes()) == ["G1A", "G1B", "G21", "G22", "G311", "G322"]
+
+
+def test_union_all_multigraph():
+    G = nx.MultiGraph()
+    G.add_edge(1, 2, key=0)
+    G.add_edge(1, 2, key=1)
+    H = nx.MultiGraph()
+    H.add_edge(3, 4, key=0)
+    H.add_edge(3, 4, key=1)
+    GH = nx.union_all([G, H])
+    assert set(GH) == set(G) | set(H)
+    assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True))
+
+
+def test_input_output():
+    l = [nx.Graph([(1, 2)]), nx.Graph([(3, 4)], awesome=True)]
+    U = nx.disjoint_union_all(l)
+    assert len(l) == 2
+    assert U.graph["awesome"]
+    C = nx.compose_all(l)
+    assert len(l) == 2
+    l = [nx.Graph([(1, 2)]), nx.Graph([(1, 2)])]
+    R = nx.intersection_all(l)
+    assert len(l) == 2
+
+
+def test_mixed_type_union():
+    with pytest.raises(nx.NetworkXError):
+        G = nx.Graph()
+        H = nx.MultiGraph()
+        I = nx.Graph()
+        U = nx.union_all([G, H, I])
+    with pytest.raises(nx.NetworkXError):
+        X = nx.Graph()
+        Y = nx.DiGraph()
+        XY = nx.union_all([X, Y])
+
+
+def test_mixed_type_disjoint_union():
+    with pytest.raises(nx.NetworkXError):
+        G = nx.Graph()
+        H = nx.MultiGraph()
+        I = nx.Graph()
+        U = nx.disjoint_union_all([G, H, I])
+    with pytest.raises(nx.NetworkXError):
+        X = nx.Graph()
+        Y = nx.DiGraph()
+        XY = nx.disjoint_union_all([X, Y])
+
+
+def test_mixed_type_intersection():
+    with pytest.raises(nx.NetworkXError):
+        G = nx.Graph()
+        H = nx.MultiGraph()
+        I = nx.Graph()
+        U = nx.intersection_all([G, H, I])
+    with pytest.raises(nx.NetworkXError):
+        X = nx.Graph()
+        Y = nx.DiGraph()
+        XY = nx.intersection_all([X, Y])
+
+
+def test_mixed_type_compose():
+    with pytest.raises(nx.NetworkXError):
+        G = nx.Graph()
+        H = nx.MultiGraph()
+        I = nx.Graph()
+        U = nx.compose_all([G, H, I])
+    with pytest.raises(nx.NetworkXError):
+        X = nx.Graph()
+        Y = nx.DiGraph()
+        XY = nx.compose_all([X, Y])
+
+
+def test_empty_union():
+    with pytest.raises(ValueError):
+        nx.union_all([])
+
+
+def test_empty_disjoint_union():
+    with pytest.raises(ValueError):
+        nx.disjoint_union_all([])
+
+
+def test_empty_compose_all():
+    with pytest.raises(ValueError):
+        nx.compose_all([])
+
+
+def test_empty_intersection_all():
+    with pytest.raises(ValueError):
+        nx.intersection_all([])
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_binary.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_binary.py
new file mode 100644
index 00000000..c907cd6f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_binary.py
@@ -0,0 +1,453 @@
+import os
+
+import pytest
+
+import networkx as nx
+from networkx.utils import edges_equal
+
+
+def test_union_attributes():
+    g = nx.Graph()
+    g.add_node(0, x=4)
+    g.add_node(1, x=5)
+    g.add_edge(0, 1, size=5)
+    g.graph["name"] = "g"
+
+    h = g.copy()
+    h.graph["name"] = "h"
+    h.graph["attr"] = "attr"
+    h.nodes[0]["x"] = 7
+
+    gh = nx.union(g, h, rename=("g", "h"))
+    assert set(gh.nodes()) == {"h0", "h1", "g0", "g1"}
+    for n in gh:
+        graph, node = n
+        assert gh.nodes[n] == eval(graph).nodes[int(node)]
+
+    assert gh.graph["attr"] == "attr"
+    assert gh.graph["name"] == "h"  # h graph attributes take precedent
+
+
+def test_intersection():
+    G = nx.Graph()
+    H = nx.Graph()
+    G.add_nodes_from([1, 2, 3, 4])
+    G.add_edge(1, 2)
+    G.add_edge(2, 3)
+    H.add_nodes_from([1, 2, 3, 4])
+    H.add_edge(2, 3)
+    H.add_edge(3, 4)
+    I = nx.intersection(G, H)
+    assert set(I.nodes()) == {1, 2, 3, 4}
+    assert sorted(I.edges()) == [(2, 3)]
+
+
+def test_intersection_node_sets_different():
+    G = nx.Graph()
+    H = nx.Graph()
+    G.add_nodes_from([1, 2, 3, 4, 7])
+    G.add_edge(1, 2)
+    G.add_edge(2, 3)
+    H.add_nodes_from([1, 2, 3, 4, 5, 6])
+    H.add_edge(2, 3)
+    H.add_edge(3, 4)
+    H.add_edge(5, 6)
+    I = nx.intersection(G, H)
+    assert set(I.nodes()) == {1, 2, 3, 4}
+    assert sorted(I.edges()) == [(2, 3)]
+
+
+def test_intersection_attributes():
+    g = nx.Graph()
+    g.add_node(0, x=4)
+    g.add_node(1, x=5)
+    g.add_edge(0, 1, size=5)
+    g.graph["name"] = "g"
+
+    h = g.copy()
+    h.graph["name"] = "h"
+    h.graph["attr"] = "attr"
+    h.nodes[0]["x"] = 7
+    gh = nx.intersection(g, h)
+
+    assert set(gh.nodes()) == set(g.nodes())
+    assert set(gh.nodes()) == set(h.nodes())
+    assert sorted(gh.edges()) == sorted(g.edges())
+
+
+def test_intersection_attributes_node_sets_different():
+    g = nx.Graph()
+    g.add_node(0, x=4)
+    g.add_node(1, x=5)
+    g.add_node(2, x=3)
+    g.add_edge(0, 1, size=5)
+    g.graph["name"] = "g"
+
+    h = g.copy()
+    h.graph["name"] = "h"
+    h.graph["attr"] = "attr"
+    h.nodes[0]["x"] = 7
+    h.remove_node(2)
+
+    gh = nx.intersection(g, h)
+    assert set(gh.nodes()) == set(h.nodes())
+    assert sorted(gh.edges()) == sorted(g.edges())
+
+
+def test_intersection_multigraph_attributes():
+    g = nx.MultiGraph()
+    g.add_edge(0, 1, key=0)
+    g.add_edge(0, 1, key=1)
+    g.add_edge(0, 1, key=2)
+    h = nx.MultiGraph()
+    h.add_edge(0, 1, key=0)
+    h.add_edge(0, 1, key=3)
+    gh = nx.intersection(g, h)
+    assert set(gh.nodes()) == set(g.nodes())
+    assert set(gh.nodes()) == set(h.nodes())
+    assert sorted(gh.edges()) == [(0, 1)]
+    assert sorted(gh.edges(keys=True)) == [(0, 1, 0)]
+
+
+def test_intersection_multigraph_attributes_node_set_different():
+    g = nx.MultiGraph()
+    g.add_edge(0, 1, key=0)
+    g.add_edge(0, 1, key=1)
+    g.add_edge(0, 1, key=2)
+    g.add_edge(0, 2, key=2)
+    g.add_edge(0, 2, key=1)
+    h = nx.MultiGraph()
+    h.add_edge(0, 1, key=0)
+    h.add_edge(0, 1, key=3)
+    gh = nx.intersection(g, h)
+    assert set(gh.nodes()) == set(h.nodes())
+    assert sorted(gh.edges()) == [(0, 1)]
+    assert sorted(gh.edges(keys=True)) == [(0, 1, 0)]
+
+
+def test_difference():
+    G = nx.Graph()
+    H = nx.Graph()
+    G.add_nodes_from([1, 2, 3, 4])
+    G.add_edge(1, 2)
+    G.add_edge(2, 3)
+    H.add_nodes_from([1, 2, 3, 4])
+    H.add_edge(2, 3)
+    H.add_edge(3, 4)
+    D = nx.difference(G, H)
+    assert set(D.nodes()) == {1, 2, 3, 4}
+    assert sorted(D.edges()) == [(1, 2)]
+    D = nx.difference(H, G)
+    assert set(D.nodes()) == {1, 2, 3, 4}
+    assert sorted(D.edges()) == [(3, 4)]
+    D = nx.symmetric_difference(G, H)
+    assert set(D.nodes()) == {1, 2, 3, 4}
+    assert sorted(D.edges()) == [(1, 2), (3, 4)]
+
+
+def test_difference2():
+    G = nx.Graph()
+    H = nx.Graph()
+    G.add_nodes_from([1, 2, 3, 4])
+    H.add_nodes_from([1, 2, 3, 4])
+    G.add_edge(1, 2)
+    H.add_edge(1, 2)
+    G.add_edge(2, 3)
+    D = nx.difference(G, H)
+    assert set(D.nodes()) == {1, 2, 3, 4}
+    assert sorted(D.edges()) == [(2, 3)]
+    D = nx.difference(H, G)
+    assert set(D.nodes()) == {1, 2, 3, 4}
+    assert sorted(D.edges()) == []
+    H.add_edge(3, 4)
+    D = nx.difference(H, G)
+    assert set(D.nodes()) == {1, 2, 3, 4}
+    assert sorted(D.edges()) == [(3, 4)]
+
+
+def test_difference_attributes():
+    g = nx.Graph()
+    g.add_node(0, x=4)
+    g.add_node(1, x=5)
+    g.add_edge(0, 1, size=5)
+    g.graph["name"] = "g"
+
+    h = g.copy()
+    h.graph["name"] = "h"
+    h.graph["attr"] = "attr"
+    h.nodes[0]["x"] = 7
+
+    gh = nx.difference(g, h)
+    assert set(gh.nodes()) == set(g.nodes())
+    assert set(gh.nodes()) == set(h.nodes())
+    assert sorted(gh.edges()) == []
+    # node and graph data should not be copied over
+    assert gh.nodes.data() != g.nodes.data()
+    assert gh.graph != g.graph
+
+
+def test_difference_multigraph_attributes():
+    g = nx.MultiGraph()
+    g.add_edge(0, 1, key=0)
+    g.add_edge(0, 1, key=1)
+    g.add_edge(0, 1, key=2)
+    h = nx.MultiGraph()
+    h.add_edge(0, 1, key=0)
+    h.add_edge(0, 1, key=3)
+    gh = nx.difference(g, h)
+    assert set(gh.nodes()) == set(g.nodes())
+    assert set(gh.nodes()) == set(h.nodes())
+    assert sorted(gh.edges()) == [(0, 1), (0, 1)]
+    assert sorted(gh.edges(keys=True)) == [(0, 1, 1), (0, 1, 2)]
+
+
+def test_difference_raise():
+    G = nx.path_graph(4)
+    H = nx.path_graph(3)
+    pytest.raises(nx.NetworkXError, nx.difference, G, H)
+    pytest.raises(nx.NetworkXError, nx.symmetric_difference, G, H)
+
+
+def test_symmetric_difference_multigraph():
+    g = nx.MultiGraph()
+    g.add_edge(0, 1, key=0)
+    g.add_edge(0, 1, key=1)
+    g.add_edge(0, 1, key=2)
+    h = nx.MultiGraph()
+    h.add_edge(0, 1, key=0)
+    h.add_edge(0, 1, key=3)
+    gh = nx.symmetric_difference(g, h)
+    assert set(gh.nodes()) == set(g.nodes())
+    assert set(gh.nodes()) == set(h.nodes())
+    assert sorted(gh.edges()) == 3 * [(0, 1)]
+    assert sorted(sorted(e) for e in gh.edges(keys=True)) == [
+        [0, 1, 1],
+        [0, 1, 2],
+        [0, 1, 3],
+    ]
+
+
+def test_union_and_compose():
+    K3 = nx.complete_graph(3)
+    P3 = nx.path_graph(3)
+
+    G1 = nx.DiGraph()
+    G1.add_edge("A", "B")
+    G1.add_edge("A", "C")
+    G1.add_edge("A", "D")
+    G2 = nx.DiGraph()
+    G2.add_edge("1", "2")
+    G2.add_edge("1", "3")
+    G2.add_edge("1", "4")
+
+    G = nx.union(G1, G2)
+    H = nx.compose(G1, G2)
+    assert edges_equal(G.edges(), H.edges())
+    assert not G.has_edge("A", 1)
+    pytest.raises(nx.NetworkXError, nx.union, K3, P3)
+    H1 = nx.union(H, G1, rename=("H", "G1"))
+    assert sorted(H1.nodes()) == [
+        "G1A",
+        "G1B",
+        "G1C",
+        "G1D",
+        "H1",
+        "H2",
+        "H3",
+        "H4",
+        "HA",
+        "HB",
+        "HC",
+        "HD",
+    ]
+
+    H2 = nx.union(H, G2, rename=("H", ""))
+    assert sorted(H2.nodes()) == [
+        "1",
+        "2",
+        "3",
+        "4",
+        "H1",
+        "H2",
+        "H3",
+        "H4",
+        "HA",
+        "HB",
+        "HC",
+        "HD",
+    ]
+
+    assert not H1.has_edge("NB", "NA")
+
+    G = nx.compose(G, G)
+    assert edges_equal(G.edges(), H.edges())
+
+    G2 = nx.union(G2, G2, rename=("", "copy"))
+    assert sorted(G2.nodes()) == [
+        "1",
+        "2",
+        "3",
+        "4",
+        "copy1",
+        "copy2",
+        "copy3",
+        "copy4",
+    ]
+
+    assert sorted(G2.neighbors("copy4")) == []
+    assert sorted(G2.neighbors("copy1")) == ["copy2", "copy3", "copy4"]
+    assert len(G) == 8
+    assert nx.number_of_edges(G) == 6
+
+    E = nx.disjoint_union(G, G)
+    assert len(E) == 16
+    assert nx.number_of_edges(E) == 12
+
+    E = nx.disjoint_union(G1, G2)
+    assert sorted(E.nodes()) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
+
+    G = nx.Graph()
+    H = nx.Graph()
+    G.add_nodes_from([(1, {"a1": 1})])
+    H.add_nodes_from([(1, {"b1": 1})])
+    R = nx.compose(G, H)
+    assert R.nodes == {1: {"a1": 1, "b1": 1}}
+
+
+def test_union_multigraph():
+    G = nx.MultiGraph()
+    G.add_edge(1, 2, key=0)
+    G.add_edge(1, 2, key=1)
+    H = nx.MultiGraph()
+    H.add_edge(3, 4, key=0)
+    H.add_edge(3, 4, key=1)
+    GH = nx.union(G, H)
+    assert set(GH) == set(G) | set(H)
+    assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True))
+
+
+def test_disjoint_union_multigraph():
+    G = nx.MultiGraph()
+    G.add_edge(0, 1, key=0)
+    G.add_edge(0, 1, key=1)
+    H = nx.MultiGraph()
+    H.add_edge(2, 3, key=0)
+    H.add_edge(2, 3, key=1)
+    GH = nx.disjoint_union(G, H)
+    assert set(GH) == set(G) | set(H)
+    assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True))
+
+
+def test_compose_multigraph():
+    G = nx.MultiGraph()
+    G.add_edge(1, 2, key=0)
+    G.add_edge(1, 2, key=1)
+    H = nx.MultiGraph()
+    H.add_edge(3, 4, key=0)
+    H.add_edge(3, 4, key=1)
+    GH = nx.compose(G, H)
+    assert set(GH) == set(G) | set(H)
+    assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True))
+    H.add_edge(1, 2, key=2)
+    GH = nx.compose(G, H)
+    assert set(GH) == set(G) | set(H)
+    assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True))
+
+
+def test_full_join_graph():
+    # Simple Graphs
+    G = nx.Graph()
+    G.add_node(0)
+    G.add_edge(1, 2)
+    H = nx.Graph()
+    H.add_edge(3, 4)
+
+    U = nx.full_join(G, H)
+    assert set(U) == set(G) | set(H)
+    assert len(U) == len(G) + len(H)
+    assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H)
+
+    # Rename
+    U = nx.full_join(G, H, rename=("g", "h"))
+    assert set(U) == {"g0", "g1", "g2", "h3", "h4"}
+    assert len(U) == len(G) + len(H)
+    assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H)
+
+    # Rename graphs with string-like nodes
+    G = nx.Graph()
+    G.add_node("a")
+    G.add_edge("b", "c")
+    H = nx.Graph()
+    H.add_edge("d", "e")
+
+    U = nx.full_join(G, H, rename=("g", "h"))
+    assert set(U) == {"ga", "gb", "gc", "hd", "he"}
+    assert len(U) == len(G) + len(H)
+    assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H)
+
+    # DiGraphs
+    G = nx.DiGraph()
+    G.add_node(0)
+    G.add_edge(1, 2)
+    H = nx.DiGraph()
+    H.add_edge(3, 4)
+
+    U = nx.full_join(G, H)
+    assert set(U) == set(G) | set(H)
+    assert len(U) == len(G) + len(H)
+    assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) * 2
+
+    # DiGraphs Rename
+    U = nx.full_join(G, H, rename=("g", "h"))
+    assert set(U) == {"g0", "g1", "g2", "h3", "h4"}
+    assert len(U) == len(G) + len(H)
+    assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) * 2
+
+
+def test_full_join_multigraph():
+    # MultiGraphs
+    G = nx.MultiGraph()
+    G.add_node(0)
+    G.add_edge(1, 2)
+    H = nx.MultiGraph()
+    H.add_edge(3, 4)
+
+    U = nx.full_join(G, H)
+    assert set(U) == set(G) | set(H)
+    assert len(U) == len(G) + len(H)
+    assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H)
+
+    # MultiGraphs rename
+    U = nx.full_join(G, H, rename=("g", "h"))
+    assert set(U) == {"g0", "g1", "g2", "h3", "h4"}
+    assert len(U) == len(G) + len(H)
+    assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H)
+
+    # MultiDiGraphs
+    G = nx.MultiDiGraph()
+    G.add_node(0)
+    G.add_edge(1, 2)
+    H = nx.MultiDiGraph()
+    H.add_edge(3, 4)
+
+    U = nx.full_join(G, H)
+    assert set(U) == set(G) | set(H)
+    assert len(U) == len(G) + len(H)
+    assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) * 2
+
+    # MultiDiGraphs rename
+    U = nx.full_join(G, H, rename=("g", "h"))
+    assert set(U) == {"g0", "g1", "g2", "h3", "h4"}
+    assert len(U) == len(G) + len(H)
+    assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) * 2
+
+
+def test_mixed_type_union():
+    G = nx.Graph()
+    H = nx.MultiGraph()
+    pytest.raises(nx.NetworkXError, nx.union, G, H)
+    pytest.raises(nx.NetworkXError, nx.disjoint_union, G, H)
+    pytest.raises(nx.NetworkXError, nx.intersection, G, H)
+    pytest.raises(nx.NetworkXError, nx.difference, G, H)
+    pytest.raises(nx.NetworkXError, nx.symmetric_difference, G, H)
+    pytest.raises(nx.NetworkXError, nx.compose, G, H)
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_product.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_product.py
new file mode 100644
index 00000000..8ee54b93
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_product.py
@@ -0,0 +1,491 @@
+import pytest
+
+import networkx as nx
+from networkx.utils import edges_equal
+
+
+def test_tensor_product_raises():
+    with pytest.raises(nx.NetworkXError):
+        P = nx.tensor_product(nx.DiGraph(), nx.Graph())
+
+
+def test_tensor_product_null():
+    null = nx.null_graph()
+    empty10 = nx.empty_graph(10)
+    K3 = nx.complete_graph(3)
+    K10 = nx.complete_graph(10)
+    P3 = nx.path_graph(3)
+    P10 = nx.path_graph(10)
+    # null graph
+    G = nx.tensor_product(null, null)
+    assert nx.is_isomorphic(G, null)
+    # null_graph X anything = null_graph and v.v.
+    G = nx.tensor_product(null, empty10)
+    assert nx.is_isomorphic(G, null)
+    G = nx.tensor_product(null, K3)
+    assert nx.is_isomorphic(G, null)
+    G = nx.tensor_product(null, K10)
+    assert nx.is_isomorphic(G, null)
+    G = nx.tensor_product(null, P3)
+    assert nx.is_isomorphic(G, null)
+    G = nx.tensor_product(null, P10)
+    assert nx.is_isomorphic(G, null)
+    G = nx.tensor_product(empty10, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.tensor_product(K3, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.tensor_product(K10, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.tensor_product(P3, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.tensor_product(P10, null)
+    assert nx.is_isomorphic(G, null)
+
+
+def test_tensor_product_size():
+    P5 = nx.path_graph(5)
+    K3 = nx.complete_graph(3)
+    K5 = nx.complete_graph(5)
+
+    G = nx.tensor_product(P5, K3)
+    assert nx.number_of_nodes(G) == 5 * 3
+    G = nx.tensor_product(K3, K5)
+    assert nx.number_of_nodes(G) == 3 * 5
+
+
+def test_tensor_product_combinations():
+    # basic smoke test, more realistic tests would be useful
+    P5 = nx.path_graph(5)
+    K3 = nx.complete_graph(3)
+    G = nx.tensor_product(P5, K3)
+    assert nx.number_of_nodes(G) == 5 * 3
+    G = nx.tensor_product(P5, nx.MultiGraph(K3))
+    assert nx.number_of_nodes(G) == 5 * 3
+    G = nx.tensor_product(nx.MultiGraph(P5), K3)
+    assert nx.number_of_nodes(G) == 5 * 3
+    G = nx.tensor_product(nx.MultiGraph(P5), nx.MultiGraph(K3))
+    assert nx.number_of_nodes(G) == 5 * 3
+
+    G = nx.tensor_product(nx.DiGraph(P5), nx.DiGraph(K3))
+    assert nx.number_of_nodes(G) == 5 * 3
+
+
+def test_tensor_product_classic_result():
+    K2 = nx.complete_graph(2)
+    G = nx.petersen_graph()
+    G = nx.tensor_product(G, K2)
+    assert nx.is_isomorphic(G, nx.desargues_graph())
+
+    G = nx.cycle_graph(5)
+    G = nx.tensor_product(G, K2)
+    assert nx.is_isomorphic(G, nx.cycle_graph(10))
+
+    G = nx.tetrahedral_graph()
+    G = nx.tensor_product(G, K2)
+    assert nx.is_isomorphic(G, nx.cubical_graph())
+
+
+def test_tensor_product_random():
+    G = nx.erdos_renyi_graph(10, 2 / 10.0)
+    H = nx.erdos_renyi_graph(10, 2 / 10.0)
+    GH = nx.tensor_product(G, H)
+
+    for u_G, u_H in GH.nodes():
+        for v_G, v_H in GH.nodes():
+            if H.has_edge(u_H, v_H) and G.has_edge(u_G, v_G):
+                assert GH.has_edge((u_G, u_H), (v_G, v_H))
+            else:
+                assert not GH.has_edge((u_G, u_H), (v_G, v_H))
+
+
+def test_cartesian_product_multigraph():
+    G = nx.MultiGraph()
+    G.add_edge(1, 2, key=0)
+    G.add_edge(1, 2, key=1)
+    H = nx.MultiGraph()
+    H.add_edge(3, 4, key=0)
+    H.add_edge(3, 4, key=1)
+    GH = nx.cartesian_product(G, H)
+    assert set(GH) == {(1, 3), (2, 3), (2, 4), (1, 4)}
+    assert {(frozenset([u, v]), k) for u, v, k in GH.edges(keys=True)} == {
+        (frozenset([u, v]), k)
+        for u, v, k in [
+            ((1, 3), (2, 3), 0),
+            ((1, 3), (2, 3), 1),
+            ((1, 3), (1, 4), 0),
+            ((1, 3), (1, 4), 1),
+            ((2, 3), (2, 4), 0),
+            ((2, 3), (2, 4), 1),
+            ((2, 4), (1, 4), 0),
+            ((2, 4), (1, 4), 1),
+        ]
+    }
+
+
+def test_cartesian_product_raises():
+    with pytest.raises(nx.NetworkXError):
+        P = nx.cartesian_product(nx.DiGraph(), nx.Graph())
+
+
+def test_cartesian_product_null():
+    null = nx.null_graph()
+    empty10 = nx.empty_graph(10)
+    K3 = nx.complete_graph(3)
+    K10 = nx.complete_graph(10)
+    P3 = nx.path_graph(3)
+    P10 = nx.path_graph(10)
+    # null graph
+    G = nx.cartesian_product(null, null)
+    assert nx.is_isomorphic(G, null)
+    # null_graph X anything = null_graph and v.v.
+    G = nx.cartesian_product(null, empty10)
+    assert nx.is_isomorphic(G, null)
+    G = nx.cartesian_product(null, K3)
+    assert nx.is_isomorphic(G, null)
+    G = nx.cartesian_product(null, K10)
+    assert nx.is_isomorphic(G, null)
+    G = nx.cartesian_product(null, P3)
+    assert nx.is_isomorphic(G, null)
+    G = nx.cartesian_product(null, P10)
+    assert nx.is_isomorphic(G, null)
+    G = nx.cartesian_product(empty10, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.cartesian_product(K3, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.cartesian_product(K10, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.cartesian_product(P3, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.cartesian_product(P10, null)
+    assert nx.is_isomorphic(G, null)
+
+
+def test_cartesian_product_size():
+    # order(GXH)=order(G)*order(H)
+    K5 = nx.complete_graph(5)
+    P5 = nx.path_graph(5)
+    K3 = nx.complete_graph(3)
+    G = nx.cartesian_product(P5, K3)
+    assert nx.number_of_nodes(G) == 5 * 3
+    assert nx.number_of_edges(G) == nx.number_of_edges(P5) * nx.number_of_nodes(
+        K3
+    ) + nx.number_of_edges(K3) * nx.number_of_nodes(P5)
+    G = nx.cartesian_product(K3, K5)
+    assert nx.number_of_nodes(G) == 3 * 5
+    assert nx.number_of_edges(G) == nx.number_of_edges(K5) * nx.number_of_nodes(
+        K3
+    ) + nx.number_of_edges(K3) * nx.number_of_nodes(K5)
+
+
+def test_cartesian_product_classic():
+    # test some classic product graphs
+    P2 = nx.path_graph(2)
+    P3 = nx.path_graph(3)
+    # cube = 2-path X 2-path
+    G = nx.cartesian_product(P2, P2)
+    G = nx.cartesian_product(P2, G)
+    assert nx.is_isomorphic(G, nx.cubical_graph())
+
+    # 3x3 grid
+    G = nx.cartesian_product(P3, P3)
+    assert nx.is_isomorphic(G, nx.grid_2d_graph(3, 3))
+
+
+def test_cartesian_product_random():
+    G = nx.erdos_renyi_graph(10, 2 / 10.0)
+    H = nx.erdos_renyi_graph(10, 2 / 10.0)
+    GH = nx.cartesian_product(G, H)
+
+    for u_G, u_H in GH.nodes():
+        for v_G, v_H in GH.nodes():
+            if (u_G == v_G and H.has_edge(u_H, v_H)) or (
+                u_H == v_H and G.has_edge(u_G, v_G)
+            ):
+                assert GH.has_edge((u_G, u_H), (v_G, v_H))
+            else:
+                assert not GH.has_edge((u_G, u_H), (v_G, v_H))
+
+
+def test_lexicographic_product_raises():
+    with pytest.raises(nx.NetworkXError):
+        P = nx.lexicographic_product(nx.DiGraph(), nx.Graph())
+
+
+def test_lexicographic_product_null():
+    null = nx.null_graph()
+    empty10 = nx.empty_graph(10)
+    K3 = nx.complete_graph(3)
+    K10 = nx.complete_graph(10)
+    P3 = nx.path_graph(3)
+    P10 = nx.path_graph(10)
+    # null graph
+    G = nx.lexicographic_product(null, null)
+    assert nx.is_isomorphic(G, null)
+    # null_graph X anything = null_graph and v.v.
+    G = nx.lexicographic_product(null, empty10)
+    assert nx.is_isomorphic(G, null)
+    G = nx.lexicographic_product(null, K3)
+    assert nx.is_isomorphic(G, null)
+    G = nx.lexicographic_product(null, K10)
+    assert nx.is_isomorphic(G, null)
+    G = nx.lexicographic_product(null, P3)
+    assert nx.is_isomorphic(G, null)
+    G = nx.lexicographic_product(null, P10)
+    assert nx.is_isomorphic(G, null)
+    G = nx.lexicographic_product(empty10, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.lexicographic_product(K3, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.lexicographic_product(K10, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.lexicographic_product(P3, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.lexicographic_product(P10, null)
+    assert nx.is_isomorphic(G, null)
+
+
+def test_lexicographic_product_size():
+    K5 = nx.complete_graph(5)
+    P5 = nx.path_graph(5)
+    K3 = nx.complete_graph(3)
+    G = nx.lexicographic_product(P5, K3)
+    assert nx.number_of_nodes(G) == 5 * 3
+    G = nx.lexicographic_product(K3, K5)
+    assert nx.number_of_nodes(G) == 3 * 5
+
+
+def test_lexicographic_product_combinations():
+    P5 = nx.path_graph(5)
+    K3 = nx.complete_graph(3)
+    G = nx.lexicographic_product(P5, K3)
+    assert nx.number_of_nodes(G) == 5 * 3
+    G = nx.lexicographic_product(nx.MultiGraph(P5), K3)
+    assert nx.number_of_nodes(G) == 5 * 3
+    G = nx.lexicographic_product(P5, nx.MultiGraph(K3))
+    assert nx.number_of_nodes(G) == 5 * 3
+    G = nx.lexicographic_product(nx.MultiGraph(P5), nx.MultiGraph(K3))
+    assert nx.number_of_nodes(G) == 5 * 3
+
+    # No classic easily found classic results for lexicographic product
+
+
+def test_lexicographic_product_random():
+    G = nx.erdos_renyi_graph(10, 2 / 10.0)
+    H = nx.erdos_renyi_graph(10, 2 / 10.0)
+    GH = nx.lexicographic_product(G, H)
+
+    for u_G, u_H in GH.nodes():
+        for v_G, v_H in GH.nodes():
+            if G.has_edge(u_G, v_G) or (u_G == v_G and H.has_edge(u_H, v_H)):
+                assert GH.has_edge((u_G, u_H), (v_G, v_H))
+            else:
+                assert not GH.has_edge((u_G, u_H), (v_G, v_H))
+
+
+def test_strong_product_raises():
+    with pytest.raises(nx.NetworkXError):
+        P = nx.strong_product(nx.DiGraph(), nx.Graph())
+
+
+def test_strong_product_null():
+    null = nx.null_graph()
+    empty10 = nx.empty_graph(10)
+    K3 = nx.complete_graph(3)
+    K10 = nx.complete_graph(10)
+    P3 = nx.path_graph(3)
+    P10 = nx.path_graph(10)
+    # null graph
+    G = nx.strong_product(null, null)
+    assert nx.is_isomorphic(G, null)
+    # null_graph X anything = null_graph and v.v.
+    G = nx.strong_product(null, empty10)
+    assert nx.is_isomorphic(G, null)
+    G = nx.strong_product(null, K3)
+    assert nx.is_isomorphic(G, null)
+    G = nx.strong_product(null, K10)
+    assert nx.is_isomorphic(G, null)
+    G = nx.strong_product(null, P3)
+    assert nx.is_isomorphic(G, null)
+    G = nx.strong_product(null, P10)
+    assert nx.is_isomorphic(G, null)
+    G = nx.strong_product(empty10, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.strong_product(K3, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.strong_product(K10, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.strong_product(P3, null)
+    assert nx.is_isomorphic(G, null)
+    G = nx.strong_product(P10, null)
+    assert nx.is_isomorphic(G, null)
+
+
+def test_strong_product_size():
+    K5 = nx.complete_graph(5)
+    P5 = nx.path_graph(5)
+    K3 = nx.complete_graph(3)
+    G = nx.strong_product(P5, K3)
+    assert nx.number_of_nodes(G) == 5 * 3
+    G = nx.strong_product(K3, K5)
+    assert nx.number_of_nodes(G) == 3 * 5
+
+
+def test_strong_product_combinations():
+    P5 = nx.path_graph(5)
+    K3 = nx.complete_graph(3)
+    G = nx.strong_product(P5, K3)
+    assert nx.number_of_nodes(G) == 5 * 3
+    G = nx.strong_product(nx.MultiGraph(P5), K3)
+    assert nx.number_of_nodes(G) == 5 * 3
+    G = nx.strong_product(P5, nx.MultiGraph(K3))
+    assert nx.number_of_nodes(G) == 5 * 3
+    G = nx.strong_product(nx.MultiGraph(P5), nx.MultiGraph(K3))
+    assert nx.number_of_nodes(G) == 5 * 3
+
+    # No classic easily found classic results for strong product
+
+
+def test_strong_product_random():
+    G = nx.erdos_renyi_graph(10, 2 / 10.0)
+    H = nx.erdos_renyi_graph(10, 2 / 10.0)
+    GH = nx.strong_product(G, H)
+
+    for u_G, u_H in GH.nodes():
+        for v_G, v_H in GH.nodes():
+            if (
+                (u_G == v_G and H.has_edge(u_H, v_H))
+                or (u_H == v_H and G.has_edge(u_G, v_G))
+                or (G.has_edge(u_G, v_G) and H.has_edge(u_H, v_H))
+            ):
+                assert GH.has_edge((u_G, u_H), (v_G, v_H))
+            else:
+                assert not GH.has_edge((u_G, u_H), (v_G, v_H))
+
+
+def test_graph_power_raises():
+    with pytest.raises(nx.NetworkXNotImplemented):
+        nx.power(nx.MultiDiGraph(), 2)
+
+
+def test_graph_power():
+    # wikipedia example for graph power
+    G = nx.cycle_graph(7)
+    G.add_edge(6, 7)
+    G.add_edge(7, 8)
+    G.add_edge(8, 9)
+    G.add_edge(9, 2)
+    H = nx.power(G, 2)
+
+    assert edges_equal(
+        list(H.edges()),
+        [
+            (0, 1),
+            (0, 2),
+            (0, 5),
+            (0, 6),
+            (0, 7),
+            (1, 9),
+            (1, 2),
+            (1, 3),
+            (1, 6),
+            (2, 3),
+            (2, 4),
+            (2, 8),
+            (2, 9),
+            (3, 4),
+            (3, 5),
+            (3, 9),
+            (4, 5),
+            (4, 6),
+            (5, 6),
+            (5, 7),
+            (6, 7),
+            (6, 8),
+            (7, 8),
+            (7, 9),
+            (8, 9),
+        ],
+    )
+
+
+def test_graph_power_negative():
+    with pytest.raises(ValueError):
+        nx.power(nx.Graph(), -1)
+
+
+def test_rooted_product_raises():
+    with pytest.raises(nx.NodeNotFound):
+        nx.rooted_product(nx.Graph(), nx.path_graph(2), 10)
+
+
+def test_rooted_product():
+    G = nx.cycle_graph(5)
+    H = nx.Graph()
+    H.add_edges_from([("a", "b"), ("b", "c"), ("b", "d")])
+    R = nx.rooted_product(G, H, "a")
+    assert len(R) == len(G) * len(H)
+    assert R.size() == G.size() + len(G) * H.size()
+
+
+def test_corona_product():
+    G = nx.cycle_graph(3)
+    H = nx.path_graph(2)
+    C = nx.corona_product(G, H)
+    assert len(C) == (len(G) * len(H)) + len(G)
+    assert C.size() == G.size() + len(G) * H.size() + len(G) * len(H)
+
+
+def test_modular_product():
+    G = nx.path_graph(3)
+    H = nx.path_graph(4)
+    M = nx.modular_product(G, H)
+    assert len(M) == len(G) * len(H)
+
+    assert edges_equal(
+        list(M.edges()),
+        [
+            ((0, 0), (1, 1)),
+            ((0, 0), (2, 2)),
+            ((0, 0), (2, 3)),
+            ((0, 1), (1, 0)),
+            ((0, 1), (1, 2)),
+            ((0, 1), (2, 3)),
+            ((0, 2), (1, 1)),
+            ((0, 2), (1, 3)),
+            ((0, 2), (2, 0)),
+            ((0, 3), (1, 2)),
+            ((0, 3), (2, 0)),
+            ((0, 3), (2, 1)),
+            ((1, 0), (2, 1)),
+            ((1, 1), (2, 0)),
+            ((1, 1), (2, 2)),
+            ((1, 2), (2, 1)),
+            ((1, 2), (2, 3)),
+            ((1, 3), (2, 2)),
+        ],
+    )
+
+
+def test_modular_product_raises():
+    G = nx.Graph([(0, 1), (1, 2), (2, 0)])
+    H = nx.Graph([(0, 1), (1, 2), (2, 0)])
+    DG = nx.DiGraph([(0, 1), (1, 2), (2, 0)])
+    DH = nx.DiGraph([(0, 1), (1, 2), (2, 0)])
+    with pytest.raises(nx.NetworkXNotImplemented):
+        nx.modular_product(G, DH)
+    with pytest.raises(nx.NetworkXNotImplemented):
+        nx.modular_product(DG, H)
+    with pytest.raises(nx.NetworkXNotImplemented):
+        nx.modular_product(DG, DH)
+
+    MG = nx.MultiGraph([(0, 1), (1, 2), (2, 0), (0, 1)])
+    MH = nx.MultiGraph([(0, 1), (1, 2), (2, 0), (0, 1)])
+    with pytest.raises(nx.NetworkXNotImplemented):
+        nx.modular_product(G, MH)
+    with pytest.raises(nx.NetworkXNotImplemented):
+        nx.modular_product(MG, H)
+    with pytest.raises(nx.NetworkXNotImplemented):
+        nx.modular_product(MG, MH)
+    with pytest.raises(nx.NetworkXNotImplemented):
+        # check multigraph with no multiedges
+        nx.modular_product(nx.MultiGraph(G), H)
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_unary.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_unary.py
new file mode 100644
index 00000000..d68e55cd
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_unary.py
@@ -0,0 +1,55 @@
+import pytest
+
+import networkx as nx
+
+
+def test_complement():
+    null = nx.null_graph()
+    empty1 = nx.empty_graph(1)
+    empty10 = nx.empty_graph(10)
+    K3 = nx.complete_graph(3)
+    K5 = nx.complete_graph(5)
+    K10 = nx.complete_graph(10)
+    P2 = nx.path_graph(2)
+    P3 = nx.path_graph(3)
+    P5 = nx.path_graph(5)
+    P10 = nx.path_graph(10)
+    # complement of the complete graph is empty
+
+    G = nx.complement(K3)
+    assert nx.is_isomorphic(G, nx.empty_graph(3))
+    G = nx.complement(K5)
+    assert nx.is_isomorphic(G, nx.empty_graph(5))
+    # for any G, G=complement(complement(G))
+    P3cc = nx.complement(nx.complement(P3))
+    assert nx.is_isomorphic(P3, P3cc)
+    nullcc = nx.complement(nx.complement(null))
+    assert nx.is_isomorphic(null, nullcc)
+    b = nx.bull_graph()
+    bcc = nx.complement(nx.complement(b))
+    assert nx.is_isomorphic(b, bcc)
+
+
+def test_complement_2():
+    G1 = nx.DiGraph()
+    G1.add_edge("A", "B")
+    G1.add_edge("A", "C")
+    G1.add_edge("A", "D")
+    G1C = nx.complement(G1)
+    assert sorted(G1C.edges()) == [
+        ("B", "A"),
+        ("B", "C"),
+        ("B", "D"),
+        ("C", "A"),
+        ("C", "B"),
+        ("C", "D"),
+        ("D", "A"),
+        ("D", "B"),
+        ("D", "C"),
+    ]
+
+
+def test_reverse1():
+    # Other tests for reverse are done by the DiGraph and MultiDigraph.
+    G1 = nx.Graph()
+    pytest.raises(nx.NetworkXError, nx.reverse, G1)