about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/community/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/algorithms/community/tests
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-4a52a71956a8d46fcb7294ac71734504bb09bcc2.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_asyn_fluid.py136
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_centrality.py85
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_divisive.py106
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_kclique.py91
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_kernighan_lin.py92
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_label_propagation.py241
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_louvain.py264
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_lukes.py152
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_modularity_max.py340
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_quality.py139
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_utils.py26
12 files changed, 1672 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/__init__.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_asyn_fluid.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_asyn_fluid.py
new file mode 100644
index 00000000..6c023be7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_asyn_fluid.py
@@ -0,0 +1,136 @@
+import pytest
+
+import networkx as nx
+from networkx import Graph, NetworkXError
+from networkx.algorithms.community import asyn_fluidc
+
+
+@pytest.mark.parametrize("graph_constructor", (nx.DiGraph, nx.MultiGraph))
+def test_raises_on_directed_and_multigraphs(graph_constructor):
+    G = graph_constructor([(0, 1), (1, 2)])
+    with pytest.raises(nx.NetworkXNotImplemented):
+        nx.community.asyn_fluidc(G, 1)
+
+
+def test_exceptions():
+    test = Graph()
+    test.add_node("a")
+    pytest.raises(NetworkXError, asyn_fluidc, test, "hi")
+    pytest.raises(NetworkXError, asyn_fluidc, test, -1)
+    pytest.raises(NetworkXError, asyn_fluidc, test, 3)
+    test.add_node("b")
+    pytest.raises(NetworkXError, asyn_fluidc, test, 1)
+
+
+def test_single_node():
+    test = Graph()
+
+    test.add_node("a")
+
+    # ground truth
+    ground_truth = {frozenset(["a"])}
+
+    communities = asyn_fluidc(test, 1)
+    result = {frozenset(c) for c in communities}
+    assert result == ground_truth
+
+
+def test_two_nodes():
+    test = Graph()
+
+    test.add_edge("a", "b")
+
+    # ground truth
+    ground_truth = {frozenset(["a"]), frozenset(["b"])}
+
+    communities = asyn_fluidc(test, 2)
+    result = {frozenset(c) for c in communities}
+    assert result == ground_truth
+
+
+def test_two_clique_communities():
+    test = Graph()
+
+    # c1
+    test.add_edge("a", "b")
+    test.add_edge("a", "c")
+    test.add_edge("b", "c")
+
+    # connection
+    test.add_edge("c", "d")
+
+    # c2
+    test.add_edge("d", "e")
+    test.add_edge("d", "f")
+    test.add_edge("f", "e")
+
+    # ground truth
+    ground_truth = {frozenset(["a", "c", "b"]), frozenset(["e", "d", "f"])}
+
+    communities = asyn_fluidc(test, 2, seed=7)
+    result = {frozenset(c) for c in communities}
+    assert result == ground_truth
+
+
+def test_five_clique_ring():
+    test = Graph()
+
+    # c1
+    test.add_edge("1a", "1b")
+    test.add_edge("1a", "1c")
+    test.add_edge("1a", "1d")
+    test.add_edge("1b", "1c")
+    test.add_edge("1b", "1d")
+    test.add_edge("1c", "1d")
+
+    # c2
+    test.add_edge("2a", "2b")
+    test.add_edge("2a", "2c")
+    test.add_edge("2a", "2d")
+    test.add_edge("2b", "2c")
+    test.add_edge("2b", "2d")
+    test.add_edge("2c", "2d")
+
+    # c3
+    test.add_edge("3a", "3b")
+    test.add_edge("3a", "3c")
+    test.add_edge("3a", "3d")
+    test.add_edge("3b", "3c")
+    test.add_edge("3b", "3d")
+    test.add_edge("3c", "3d")
+
+    # c4
+    test.add_edge("4a", "4b")
+    test.add_edge("4a", "4c")
+    test.add_edge("4a", "4d")
+    test.add_edge("4b", "4c")
+    test.add_edge("4b", "4d")
+    test.add_edge("4c", "4d")
+
+    # c5
+    test.add_edge("5a", "5b")
+    test.add_edge("5a", "5c")
+    test.add_edge("5a", "5d")
+    test.add_edge("5b", "5c")
+    test.add_edge("5b", "5d")
+    test.add_edge("5c", "5d")
+
+    # connections
+    test.add_edge("1a", "2c")
+    test.add_edge("2a", "3c")
+    test.add_edge("3a", "4c")
+    test.add_edge("4a", "5c")
+    test.add_edge("5a", "1c")
+
+    # ground truth
+    ground_truth = {
+        frozenset(["1a", "1b", "1c", "1d"]),
+        frozenset(["2a", "2b", "2c", "2d"]),
+        frozenset(["3a", "3b", "3c", "3d"]),
+        frozenset(["4a", "4b", "4c", "4d"]),
+        frozenset(["5a", "5b", "5c", "5d"]),
+    }
+
+    communities = asyn_fluidc(test, 5, seed=9)
+    result = {frozenset(c) for c in communities}
+    assert result == ground_truth
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_centrality.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_centrality.py
new file mode 100644
index 00000000..1a8982f0
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_centrality.py
@@ -0,0 +1,85 @@
+"""Unit tests for the :mod:`networkx.algorithms.community.centrality`
+module.
+
+"""
+
+from operator import itemgetter
+
+import networkx as nx
+
+
+def set_of_sets(iterable):
+    return set(map(frozenset, iterable))
+
+
+def validate_communities(result, expected):
+    assert set_of_sets(result) == set_of_sets(expected)
+
+
+def validate_possible_communities(result, *expected):
+    assert any(set_of_sets(result) == set_of_sets(p) for p in expected)
+
+
+class TestGirvanNewman:
+    """Unit tests for the
+    :func:`networkx.algorithms.community.centrality.girvan_newman`
+    function.
+
+    """
+
+    def test_no_edges(self):
+        G = nx.empty_graph(3)
+        communities = list(nx.community.girvan_newman(G))
+        assert len(communities) == 1
+        validate_communities(communities[0], [{0}, {1}, {2}])
+
+    def test_undirected(self):
+        # Start with the graph .-.-.-.
+        G = nx.path_graph(4)
+        communities = list(nx.community.girvan_newman(G))
+        assert len(communities) == 3
+        # After one removal, we get the graph .-. .-.
+        validate_communities(communities[0], [{0, 1}, {2, 3}])
+        # After the next, we get the graph .-. . ., but there are two
+        # symmetric possible versions.
+        validate_possible_communities(
+            communities[1], [{0}, {1}, {2, 3}], [{0, 1}, {2}, {3}]
+        )
+        # After the last removal, we always get the empty graph.
+        validate_communities(communities[2], [{0}, {1}, {2}, {3}])
+
+    def test_directed(self):
+        G = nx.DiGraph(nx.path_graph(4))
+        communities = list(nx.community.girvan_newman(G))
+        assert len(communities) == 3
+        validate_communities(communities[0], [{0, 1}, {2, 3}])
+        validate_possible_communities(
+            communities[1], [{0}, {1}, {2, 3}], [{0, 1}, {2}, {3}]
+        )
+        validate_communities(communities[2], [{0}, {1}, {2}, {3}])
+
+    def test_selfloops(self):
+        G = nx.path_graph(4)
+        G.add_edge(0, 0)
+        G.add_edge(2, 2)
+        communities = list(nx.community.girvan_newman(G))
+        assert len(communities) == 3
+        validate_communities(communities[0], [{0, 1}, {2, 3}])
+        validate_possible_communities(
+            communities[1], [{0}, {1}, {2, 3}], [{0, 1}, {2}, {3}]
+        )
+        validate_communities(communities[2], [{0}, {1}, {2}, {3}])
+
+    def test_most_valuable_edge(self):
+        G = nx.Graph()
+        G.add_weighted_edges_from([(0, 1, 3), (1, 2, 2), (2, 3, 1)])
+        # Let the most valuable edge be the one with the highest weight.
+
+        def heaviest(G):
+            return max(G.edges(data="weight"), key=itemgetter(2))[:2]
+
+        communities = list(nx.community.girvan_newman(G, heaviest))
+        assert len(communities) == 3
+        validate_communities(communities[0], [{0}, {1, 2, 3}])
+        validate_communities(communities[1], [{0}, {1}, {2, 3}])
+        validate_communities(communities[2], [{0}, {1}, {2}, {3}])
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_divisive.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_divisive.py
new file mode 100644
index 00000000..6331503f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_divisive.py
@@ -0,0 +1,106 @@
+import pytest
+
+import networkx as nx
+
+
+def test_edge_betweenness_partition():
+    G = nx.barbell_graph(3, 0)
+    C = nx.community.edge_betweenness_partition(G, 2)
+    answer = [{0, 1, 2}, {3, 4, 5}]
+    assert len(C) == len(answer)
+    for s in answer:
+        assert s in C
+
+    G = nx.barbell_graph(3, 1)
+    C = nx.community.edge_betweenness_partition(G, 3)
+    answer = [{0, 1, 2}, {4, 5, 6}, {3}]
+    assert len(C) == len(answer)
+    for s in answer:
+        assert s in C
+
+    C = nx.community.edge_betweenness_partition(G, 7)
+    answer = [{n} for n in G]
+    assert len(C) == len(answer)
+    for s in answer:
+        assert s in C
+
+    C = nx.community.edge_betweenness_partition(G, 1)
+    assert C == [set(G)]
+
+    C = nx.community.edge_betweenness_partition(G, 1, weight="weight")
+    assert C == [set(G)]
+
+    with pytest.raises(nx.NetworkXError):
+        nx.community.edge_betweenness_partition(G, 0)
+
+    with pytest.raises(nx.NetworkXError):
+        nx.community.edge_betweenness_partition(G, -1)
+
+    with pytest.raises(nx.NetworkXError):
+        nx.community.edge_betweenness_partition(G, 10)
+
+
+def test_edge_current_flow_betweenness_partition():
+    pytest.importorskip("scipy")
+
+    G = nx.barbell_graph(3, 0)
+    C = nx.community.edge_current_flow_betweenness_partition(G, 2)
+    answer = [{0, 1, 2}, {3, 4, 5}]
+    assert len(C) == len(answer)
+    for s in answer:
+        assert s in C
+
+    G = nx.barbell_graph(3, 1)
+    C = nx.community.edge_current_flow_betweenness_partition(G, 2)
+    answers = [[{0, 1, 2, 3}, {4, 5, 6}], [{0, 1, 2}, {3, 4, 5, 6}]]
+    assert len(C) == len(answers[0])
+    assert any(all(s in answer for s in C) for answer in answers)
+
+    C = nx.community.edge_current_flow_betweenness_partition(G, 3)
+    answer = [{0, 1, 2}, {4, 5, 6}, {3}]
+    assert len(C) == len(answer)
+    for s in answer:
+        assert s in C
+
+    C = nx.community.edge_current_flow_betweenness_partition(G, 4)
+    answers = [[{1, 2}, {4, 5, 6}, {3}, {0}], [{0, 1, 2}, {5, 6}, {3}, {4}]]
+    assert len(C) == len(answers[0])
+    assert any(all(s in answer for s in C) for answer in answers)
+
+    C = nx.community.edge_current_flow_betweenness_partition(G, 5)
+    answer = [{1, 2}, {5, 6}, {3}, {0}, {4}]
+    assert len(C) == len(answer)
+    for s in answer:
+        assert s in C
+
+    C = nx.community.edge_current_flow_betweenness_partition(G, 6)
+    answers = [[{2}, {5, 6}, {3}, {0}, {4}, {1}], [{1, 2}, {6}, {3}, {0}, {4}, {5}]]
+    assert len(C) == len(answers[0])
+    assert any(all(s in answer for s in C) for answer in answers)
+
+    C = nx.community.edge_current_flow_betweenness_partition(G, 7)
+    answer = [{n} for n in G]
+    assert len(C) == len(answer)
+    for s in answer:
+        assert s in C
+
+    C = nx.community.edge_current_flow_betweenness_partition(G, 1)
+    assert C == [set(G)]
+
+    C = nx.community.edge_current_flow_betweenness_partition(G, 1, weight="weight")
+    assert C == [set(G)]
+
+    with pytest.raises(nx.NetworkXError):
+        nx.community.edge_current_flow_betweenness_partition(G, 0)
+
+    with pytest.raises(nx.NetworkXError):
+        nx.community.edge_current_flow_betweenness_partition(G, -1)
+
+    with pytest.raises(nx.NetworkXError):
+        nx.community.edge_current_flow_betweenness_partition(G, 10)
+
+    N = 10
+    G = nx.empty_graph(N)
+    for i in range(2, N - 1):
+        C = nx.community.edge_current_flow_betweenness_partition(G, i)
+        assert C == [{n} for n in G]
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_kclique.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_kclique.py
new file mode 100644
index 00000000..aa0b7e82
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_kclique.py
@@ -0,0 +1,91 @@
+from itertools import combinations
+
+import pytest
+
+import networkx as nx
+
+
+def test_overlapping_K5():
+    G = nx.Graph()
+    G.add_edges_from(combinations(range(5), 2))  # Add a five clique
+    G.add_edges_from(combinations(range(2, 7), 2))  # Add another five clique
+    c = list(nx.community.k_clique_communities(G, 4))
+    assert c == [frozenset(range(7))]
+    c = set(nx.community.k_clique_communities(G, 5))
+    assert c == {frozenset(range(5)), frozenset(range(2, 7))}
+
+
+def test_isolated_K5():
+    G = nx.Graph()
+    G.add_edges_from(combinations(range(5), 2))  # Add a five clique
+    G.add_edges_from(combinations(range(5, 10), 2))  # Add another five clique
+    c = set(nx.community.k_clique_communities(G, 5))
+    assert c == {frozenset(range(5)), frozenset(range(5, 10))}
+
+
+class TestZacharyKarateClub:
+    def setup_method(self):
+        self.G = nx.karate_club_graph()
+
+    def _check_communities(self, k, expected):
+        communities = set(nx.community.k_clique_communities(self.G, k))
+        assert communities == expected
+
+    def test_k2(self):
+        # clique percolation with k=2 is just connected components
+        expected = {frozenset(self.G)}
+        self._check_communities(2, expected)
+
+    def test_k3(self):
+        comm1 = [
+            0,
+            1,
+            2,
+            3,
+            7,
+            8,
+            12,
+            13,
+            14,
+            15,
+            17,
+            18,
+            19,
+            20,
+            21,
+            22,
+            23,
+            26,
+            27,
+            28,
+            29,
+            30,
+            31,
+            32,
+            33,
+        ]
+        comm2 = [0, 4, 5, 6, 10, 16]
+        comm3 = [24, 25, 31]
+        expected = {frozenset(comm1), frozenset(comm2), frozenset(comm3)}
+        self._check_communities(3, expected)
+
+    def test_k4(self):
+        expected = {
+            frozenset([0, 1, 2, 3, 7, 13]),
+            frozenset([8, 32, 30, 33]),
+            frozenset([32, 33, 29, 23]),
+        }
+        self._check_communities(4, expected)
+
+    def test_k5(self):
+        expected = {frozenset([0, 1, 2, 3, 7, 13])}
+        self._check_communities(5, expected)
+
+    def test_k6(self):
+        expected = set()
+        self._check_communities(6, expected)
+
+
+def test_bad_k():
+    with pytest.raises(nx.NetworkXError):
+        list(nx.community.k_clique_communities(nx.Graph(), 1))
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_kernighan_lin.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_kernighan_lin.py
new file mode 100644
index 00000000..25d53d5f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_kernighan_lin.py
@@ -0,0 +1,92 @@
+"""Unit tests for the :mod:`networkx.algorithms.community.kernighan_lin`
+module.
+"""
+
+from itertools import permutations
+
+import pytest
+
+import networkx as nx
+from networkx.algorithms.community import kernighan_lin_bisection
+
+
+def assert_partition_equal(x, y):
+    assert set(map(frozenset, x)) == set(map(frozenset, y))
+
+
+def test_partition():
+    G = nx.barbell_graph(3, 0)
+    C = kernighan_lin_bisection(G)
+    assert_partition_equal(C, [{0, 1, 2}, {3, 4, 5}])
+
+
+def test_partition_argument():
+    G = nx.barbell_graph(3, 0)
+    partition = [{0, 1, 2}, {3, 4, 5}]
+    C = kernighan_lin_bisection(G, partition)
+    assert_partition_equal(C, partition)
+
+
+def test_partition_argument_non_integer_nodes():
+    G = nx.Graph([("A", "B"), ("A", "C"), ("B", "C"), ("C", "D")])
+    partition = ({"A", "B"}, {"C", "D"})
+    C = kernighan_lin_bisection(G, partition)
+    assert_partition_equal(C, partition)
+
+
+def test_seed_argument():
+    G = nx.barbell_graph(3, 0)
+    C = kernighan_lin_bisection(G, seed=1)
+    assert_partition_equal(C, [{0, 1, 2}, {3, 4, 5}])
+
+
+def test_non_disjoint_partition():
+    with pytest.raises(nx.NetworkXError):
+        G = nx.barbell_graph(3, 0)
+        partition = ({0, 1, 2}, {2, 3, 4, 5})
+        kernighan_lin_bisection(G, partition)
+
+
+def test_too_many_blocks():
+    with pytest.raises(nx.NetworkXError):
+        G = nx.barbell_graph(3, 0)
+        partition = ({0, 1}, {2}, {3, 4, 5})
+        kernighan_lin_bisection(G, partition)
+
+
+def test_multigraph():
+    G = nx.cycle_graph(4)
+    M = nx.MultiGraph(G.edges())
+    M.add_edges_from(G.edges())
+    M.remove_edge(1, 2)
+    for labels in permutations(range(4)):
+        mapping = dict(zip(M, labels))
+        A, B = kernighan_lin_bisection(nx.relabel_nodes(M, mapping), seed=0)
+        assert_partition_equal(
+            [A, B], [{mapping[0], mapping[1]}, {mapping[2], mapping[3]}]
+        )
+
+
+def test_max_iter_argument():
+    G = nx.Graph(
+        [
+            ("A", "B", {"weight": 1}),
+            ("A", "C", {"weight": 2}),
+            ("A", "D", {"weight": 3}),
+            ("A", "E", {"weight": 2}),
+            ("A", "F", {"weight": 4}),
+            ("B", "C", {"weight": 1}),
+            ("B", "D", {"weight": 4}),
+            ("B", "E", {"weight": 2}),
+            ("B", "F", {"weight": 1}),
+            ("C", "D", {"weight": 3}),
+            ("C", "E", {"weight": 2}),
+            ("C", "F", {"weight": 1}),
+            ("D", "E", {"weight": 4}),
+            ("D", "F", {"weight": 3}),
+            ("E", "F", {"weight": 2}),
+        ]
+    )
+    partition = ({"A", "B", "C"}, {"D", "E", "F"})
+    C = kernighan_lin_bisection(G, partition, max_iter=1)
+    assert_partition_equal(C, ({"A", "F", "C"}, {"D", "E", "B"}))
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_label_propagation.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_label_propagation.py
new file mode 100644
index 00000000..4be72dbf
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_label_propagation.py
@@ -0,0 +1,241 @@
+from itertools import chain, combinations
+
+import pytest
+
+import networkx as nx
+
+
+def test_directed_not_supported():
+    with pytest.raises(nx.NetworkXNotImplemented):
+        # not supported for directed graphs
+        test = nx.DiGraph()
+        test.add_edge("a", "b")
+        test.add_edge("a", "c")
+        test.add_edge("b", "d")
+        result = nx.community.label_propagation_communities(test)
+
+
+def test_iterator_vs_iterable():
+    G = nx.empty_graph("a")
+    assert list(nx.community.label_propagation_communities(G)) == [{"a"}]
+    for community in nx.community.label_propagation_communities(G):
+        assert community == {"a"}
+    pytest.raises(TypeError, next, nx.community.label_propagation_communities(G))
+
+
+def test_one_node():
+    test = nx.Graph()
+    test.add_node("a")
+
+    # The expected communities are:
+    ground_truth = {frozenset(["a"])}
+
+    communities = nx.community.label_propagation_communities(test)
+    result = {frozenset(c) for c in communities}
+    assert result == ground_truth
+
+
+def test_unconnected_communities():
+    test = nx.Graph()
+    # community 1
+    test.add_edge("a", "c")
+    test.add_edge("a", "d")
+    test.add_edge("d", "c")
+    # community 2
+    test.add_edge("b", "e")
+    test.add_edge("e", "f")
+    test.add_edge("f", "b")
+
+    # The expected communities are:
+    ground_truth = {frozenset(["a", "c", "d"]), frozenset(["b", "e", "f"])}
+
+    communities = nx.community.label_propagation_communities(test)
+    result = {frozenset(c) for c in communities}
+    assert result == ground_truth
+
+
+def test_connected_communities():
+    test = nx.Graph()
+    # community 1
+    test.add_edge("a", "b")
+    test.add_edge("c", "a")
+    test.add_edge("c", "b")
+    test.add_edge("d", "a")
+    test.add_edge("d", "b")
+    test.add_edge("d", "c")
+    test.add_edge("e", "a")
+    test.add_edge("e", "b")
+    test.add_edge("e", "c")
+    test.add_edge("e", "d")
+    # community 2
+    test.add_edge("1", "2")
+    test.add_edge("3", "1")
+    test.add_edge("3", "2")
+    test.add_edge("4", "1")
+    test.add_edge("4", "2")
+    test.add_edge("4", "3")
+    test.add_edge("5", "1")
+    test.add_edge("5", "2")
+    test.add_edge("5", "3")
+    test.add_edge("5", "4")
+    # edge between community 1 and 2
+    test.add_edge("a", "1")
+    # community 3
+    test.add_edge("x", "y")
+    # community 4 with only a single node
+    test.add_node("z")
+
+    # The expected communities are:
+    ground_truth1 = {
+        frozenset(["a", "b", "c", "d", "e"]),
+        frozenset(["1", "2", "3", "4", "5"]),
+        frozenset(["x", "y"]),
+        frozenset(["z"]),
+    }
+    ground_truth2 = {
+        frozenset(["a", "b", "c", "d", "e", "1", "2", "3", "4", "5"]),
+        frozenset(["x", "y"]),
+        frozenset(["z"]),
+    }
+    ground_truth = (ground_truth1, ground_truth2)
+
+    communities = nx.community.label_propagation_communities(test)
+    result = {frozenset(c) for c in communities}
+    assert result in ground_truth
+
+
+def test_termination():
+    # ensure termination of asyn_lpa_communities in two cases
+    # that led to an endless loop in a previous version
+    test1 = nx.karate_club_graph()
+    test2 = nx.caveman_graph(2, 10)
+    test2.add_edges_from([(0, 20), (20, 10)])
+    nx.community.asyn_lpa_communities(test1)
+    nx.community.asyn_lpa_communities(test2)
+
+
+class TestAsynLpaCommunities:
+    def _check_communities(self, G, expected):
+        """Checks that the communities computed from the given graph ``G``
+        using the :func:`~networkx.asyn_lpa_communities` function match
+        the set of nodes given in ``expected``.
+
+        ``expected`` must be a :class:`set` of :class:`frozenset`
+        instances, each element of which is a node in the graph.
+
+        """
+        communities = nx.community.asyn_lpa_communities(G)
+        result = {frozenset(c) for c in communities}
+        assert result == expected
+
+    def test_null_graph(self):
+        G = nx.null_graph()
+        ground_truth = set()
+        self._check_communities(G, ground_truth)
+
+    def test_single_node(self):
+        G = nx.empty_graph(1)
+        ground_truth = {frozenset([0])}
+        self._check_communities(G, ground_truth)
+
+    def test_simple_communities(self):
+        # This graph is the disjoint union of two triangles.
+        G = nx.Graph(["ab", "ac", "bc", "de", "df", "fe"])
+        ground_truth = {frozenset("abc"), frozenset("def")}
+        self._check_communities(G, ground_truth)
+
+    def test_seed_argument(self):
+        G = nx.Graph(["ab", "ac", "bc", "de", "df", "fe"])
+        ground_truth = {frozenset("abc"), frozenset("def")}
+        communities = nx.community.asyn_lpa_communities(G, seed=1)
+        result = {frozenset(c) for c in communities}
+        assert result == ground_truth
+
+    def test_several_communities(self):
+        # This graph is the disjoint union of five triangles.
+        ground_truth = {frozenset(range(3 * i, 3 * (i + 1))) for i in range(5)}
+        edges = chain.from_iterable(combinations(c, 2) for c in ground_truth)
+        G = nx.Graph(edges)
+        self._check_communities(G, ground_truth)
+
+
+class TestFastLabelPropagationCommunities:
+    N = 100  # number of nodes
+    K = 15  # average node degree
+
+    def _check_communities(self, G, truth, weight=None, seed=42):
+        C = nx.community.fast_label_propagation_communities(G, weight=weight, seed=seed)
+        assert {frozenset(c) for c in C} == truth
+
+    def test_null_graph(self):
+        G = nx.null_graph()
+        truth = set()
+        self._check_communities(G, truth)
+
+    def test_empty_graph(self):
+        G = nx.empty_graph(self.N)
+        truth = {frozenset([i]) for i in G}
+        self._check_communities(G, truth)
+
+    def test_star_graph(self):
+        G = nx.star_graph(self.N)
+        truth = {frozenset(G)}
+        self._check_communities(G, truth)
+
+    def test_complete_graph(self):
+        G = nx.complete_graph(self.N)
+        truth = {frozenset(G)}
+        self._check_communities(G, truth)
+
+    def test_bipartite_graph(self):
+        G = nx.complete_bipartite_graph(self.N // 2, self.N // 2)
+        truth = {frozenset(G)}
+        self._check_communities(G, truth)
+
+    def test_random_graph(self):
+        G = nx.gnm_random_graph(self.N, self.N * self.K // 2, seed=42)
+        truth = {frozenset(G)}
+        self._check_communities(G, truth)
+
+    def test_disjoin_cliques(self):
+        G = nx.Graph(["ab", "AB", "AC", "BC", "12", "13", "14", "23", "24", "34"])
+        truth = {frozenset("ab"), frozenset("ABC"), frozenset("1234")}
+        self._check_communities(G, truth)
+
+    def test_ring_of_cliques(self):
+        N, K = self.N, self.K
+        G = nx.ring_of_cliques(N, K)
+        truth = {frozenset([K * i + k for k in range(K)]) for i in range(N)}
+        self._check_communities(G, truth)
+
+    def test_larger_graph(self):
+        G = nx.gnm_random_graph(100 * self.N, 50 * self.N * self.K, seed=42)
+        nx.community.fast_label_propagation_communities(G)
+
+    def test_graph_type(self):
+        G1 = nx.complete_graph(self.N, nx.MultiDiGraph())
+        G2 = nx.MultiGraph(G1)
+        G3 = nx.DiGraph(G1)
+        G4 = nx.Graph(G1)
+        truth = {frozenset(G1)}
+        self._check_communities(G1, truth)
+        self._check_communities(G2, truth)
+        self._check_communities(G3, truth)
+        self._check_communities(G4, truth)
+
+    def test_weight_argument(self):
+        G = nx.MultiDiGraph()
+        G.add_edge(1, 2, weight=1.41)
+        G.add_edge(2, 1, weight=1.41)
+        G.add_edge(2, 3)
+        G.add_edge(3, 4, weight=3.14)
+        truth = {frozenset({1, 2}), frozenset({3, 4})}
+        self._check_communities(G, truth, weight="weight")
+
+    def test_seed_argument(self):
+        G = nx.karate_club_graph()
+        C = nx.community.fast_label_propagation_communities(G, seed=2023)
+        truth = {frozenset(c) for c in C}
+        self._check_communities(G, truth, seed=2023)
+        # smoke test that seed=None works
+        C = nx.community.fast_label_propagation_communities(G, seed=None)
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_louvain.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_louvain.py
new file mode 100644
index 00000000..816e6f14
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_louvain.py
@@ -0,0 +1,264 @@
+import pytest
+
+import networkx as nx
+
+
+def test_modularity_increase():
+    G = nx.LFR_benchmark_graph(
+        250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10
+    )
+    partition = [{u} for u in G.nodes()]
+    mod = nx.community.modularity(G, partition)
+    partition = nx.community.louvain_communities(G)
+
+    assert nx.community.modularity(G, partition) > mod
+
+
+def test_valid_partition():
+    G = nx.LFR_benchmark_graph(
+        250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10
+    )
+    H = G.to_directed()
+    partition = nx.community.louvain_communities(G)
+    partition2 = nx.community.louvain_communities(H)
+
+    assert nx.community.is_partition(G, partition)
+    assert nx.community.is_partition(H, partition2)
+
+
+def test_karate_club_partition():
+    G = nx.karate_club_graph()
+    part = [
+        {0, 1, 2, 3, 7, 9, 11, 12, 13, 17, 19, 21},
+        {16, 4, 5, 6, 10},
+        {23, 25, 27, 28, 24, 31},
+        {32, 33, 8, 14, 15, 18, 20, 22, 26, 29, 30},
+    ]
+    partition = nx.community.louvain_communities(G, seed=2, weight=None)
+
+    assert part == partition
+
+
+def test_partition_iterator():
+    G = nx.path_graph(15)
+    parts_iter = nx.community.louvain_partitions(G, seed=42)
+    first_part = next(parts_iter)
+    first_copy = [s.copy() for s in first_part]
+
+    # gh-5901 reports sets changing after next partition is yielded
+    assert first_copy[0] == first_part[0]
+    second_part = next(parts_iter)
+    assert first_copy[0] == first_part[0]
+
+
+def test_undirected_selfloops():
+    G = nx.karate_club_graph()
+    expected_partition = nx.community.louvain_communities(G, seed=2, weight=None)
+    part = [
+        {0, 1, 2, 3, 7, 9, 11, 12, 13, 17, 19, 21},
+        {16, 4, 5, 6, 10},
+        {23, 25, 27, 28, 24, 31},
+        {32, 33, 8, 14, 15, 18, 20, 22, 26, 29, 30},
+    ]
+    assert expected_partition == part
+
+    G.add_weighted_edges_from([(i, i, i * 1000) for i in range(9)])
+    # large self-loop weight impacts partition
+    partition = nx.community.louvain_communities(G, seed=2, weight="weight")
+    assert part != partition
+
+    # small self-loop weights aren't enough to impact partition in this graph
+    partition = nx.community.louvain_communities(G, seed=2, weight=None)
+    assert part == partition
+
+
+def test_directed_selfloops():
+    G = nx.DiGraph()
+    G.add_nodes_from(range(11))
+    G_edges = [
+        (0, 2),
+        (0, 1),
+        (1, 0),
+        (2, 1),
+        (2, 0),
+        (3, 4),
+        (4, 3),
+        (7, 8),
+        (8, 7),
+        (9, 10),
+        (10, 9),
+    ]
+    G.add_edges_from(G_edges)
+    G_expected_partition = nx.community.louvain_communities(G, seed=123, weight=None)
+
+    G.add_weighted_edges_from([(i, i, i * 1000) for i in range(3)])
+    # large self-loop weight impacts partition
+    G_partition = nx.community.louvain_communities(G, seed=123, weight="weight")
+    assert G_partition != G_expected_partition
+
+    # small self-loop weights aren't enough to impact partition in this graph
+    G_partition = nx.community.louvain_communities(G, seed=123, weight=None)
+    assert G_partition == G_expected_partition
+
+
+def test_directed_partition():
+    """
+    Test 2 cases that were looping infinitely
+    from issues #5175 and #5704
+    """
+    G = nx.DiGraph()
+    H = nx.DiGraph()
+    G.add_nodes_from(range(10))
+    H.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
+    G_edges = [
+        (0, 2),
+        (0, 1),
+        (1, 0),
+        (2, 1),
+        (2, 0),
+        (3, 4),
+        (4, 3),
+        (7, 8),
+        (8, 7),
+        (9, 10),
+        (10, 9),
+    ]
+    H_edges = [
+        (1, 2),
+        (1, 6),
+        (1, 9),
+        (2, 3),
+        (2, 4),
+        (2, 5),
+        (3, 4),
+        (4, 3),
+        (4, 5),
+        (5, 4),
+        (6, 7),
+        (6, 8),
+        (9, 10),
+        (9, 11),
+        (10, 11),
+        (11, 10),
+    ]
+    G.add_edges_from(G_edges)
+    H.add_edges_from(H_edges)
+
+    G_expected_partition = [{0, 1, 2}, {3, 4}, {5}, {6}, {8, 7}, {9, 10}]
+    G_partition = nx.community.louvain_communities(G, seed=123, weight=None)
+
+    H_expected_partition = [{2, 3, 4, 5}, {8, 1, 6, 7}, {9, 10, 11}]
+    H_partition = nx.community.louvain_communities(H, seed=123, weight=None)
+
+    assert G_partition == G_expected_partition
+    assert H_partition == H_expected_partition
+
+
+def test_none_weight_param():
+    G = nx.karate_club_graph()
+    nx.set_edge_attributes(
+        G, {edge: i * i for i, edge in enumerate(G.edges)}, name="foo"
+    )
+
+    part = [
+        {0, 1, 2, 3, 7, 9, 11, 12, 13, 17, 19, 21},
+        {16, 4, 5, 6, 10},
+        {23, 25, 27, 28, 24, 31},
+        {32, 33, 8, 14, 15, 18, 20, 22, 26, 29, 30},
+    ]
+    partition1 = nx.community.louvain_communities(G, weight=None, seed=2)
+    partition2 = nx.community.louvain_communities(G, weight="foo", seed=2)
+    partition3 = nx.community.louvain_communities(G, weight="weight", seed=2)
+
+    assert part == partition1
+    assert part != partition2
+    assert part != partition3
+    assert partition2 != partition3
+
+
+def test_quality():
+    G = nx.LFR_benchmark_graph(
+        250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10
+    )
+    H = nx.gn_graph(200, seed=1234)
+    I = nx.MultiGraph(G)
+    J = nx.MultiDiGraph(H)
+
+    partition = nx.community.louvain_communities(G)
+    partition2 = nx.community.louvain_communities(H)
+    partition3 = nx.community.louvain_communities(I)
+    partition4 = nx.community.louvain_communities(J)
+
+    quality = nx.community.partition_quality(G, partition)[0]
+    quality2 = nx.community.partition_quality(H, partition2)[0]
+    quality3 = nx.community.partition_quality(I, partition3)[0]
+    quality4 = nx.community.partition_quality(J, partition4)[0]
+
+    assert quality >= 0.65
+    assert quality2 >= 0.65
+    assert quality3 >= 0.65
+    assert quality4 >= 0.65
+
+
+def test_multigraph():
+    G = nx.karate_club_graph()
+    H = nx.MultiGraph(G)
+    G.add_edge(0, 1, weight=10)
+    H.add_edge(0, 1, weight=9)
+    G.add_edge(0, 9, foo=20)
+    H.add_edge(0, 9, foo=20)
+
+    partition1 = nx.community.louvain_communities(G, seed=1234)
+    partition2 = nx.community.louvain_communities(H, seed=1234)
+    partition3 = nx.community.louvain_communities(H, weight="foo", seed=1234)
+
+    assert partition1 == partition2 != partition3
+
+
+def test_resolution():
+    G = nx.LFR_benchmark_graph(
+        250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10
+    )
+
+    partition1 = nx.community.louvain_communities(G, resolution=0.5, seed=12)
+    partition2 = nx.community.louvain_communities(G, seed=12)
+    partition3 = nx.community.louvain_communities(G, resolution=2, seed=12)
+
+    assert len(partition1) <= len(partition2) <= len(partition3)
+
+
+def test_threshold():
+    G = nx.LFR_benchmark_graph(
+        250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10
+    )
+    partition1 = nx.community.louvain_communities(G, threshold=0.3, seed=2)
+    partition2 = nx.community.louvain_communities(G, seed=2)
+    mod1 = nx.community.modularity(G, partition1)
+    mod2 = nx.community.modularity(G, partition2)
+
+    assert mod1 <= mod2
+
+
+def test_empty_graph():
+    G = nx.Graph()
+    G.add_nodes_from(range(5))
+    expected = [{0}, {1}, {2}, {3}, {4}]
+    assert nx.community.louvain_communities(G) == expected
+
+
+def test_max_level():
+    G = nx.LFR_benchmark_graph(
+        250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10
+    )
+    parts_iter = nx.community.louvain_partitions(G, seed=42)
+    for max_level, expected in enumerate(parts_iter, 1):
+        partition = nx.community.louvain_communities(G, max_level=max_level, seed=42)
+        assert partition == expected
+    assert max_level > 1  # Ensure we are actually testing max_level
+    # max_level is an upper limit; it's okay if we stop before it's hit.
+    partition = nx.community.louvain_communities(G, max_level=max_level + 1, seed=42)
+    assert partition == expected
+    with pytest.raises(
+        ValueError, match="max_level argument must be a positive integer"
+    ):
+        nx.community.louvain_communities(G, max_level=0)
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_lukes.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_lukes.py
new file mode 100644
index 00000000..cfa48f0f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_lukes.py
@@ -0,0 +1,152 @@
+from itertools import product
+
+import pytest
+
+import networkx as nx
+
+EWL = "e_weight"
+NWL = "n_weight"
+
+
+# first test from the Lukes original paper
+def paper_1_case(float_edge_wt=False, explicit_node_wt=True, directed=False):
+    # problem-specific constants
+    limit = 3
+
+    # configuration
+    if float_edge_wt:
+        shift = 0.001
+    else:
+        shift = 0
+
+    if directed:
+        example_1 = nx.DiGraph()
+    else:
+        example_1 = nx.Graph()
+
+    # graph creation
+    example_1.add_edge(1, 2, **{EWL: 3 + shift})
+    example_1.add_edge(1, 4, **{EWL: 2 + shift})
+    example_1.add_edge(2, 3, **{EWL: 4 + shift})
+    example_1.add_edge(2, 5, **{EWL: 6 + shift})
+
+    # node weights
+    if explicit_node_wt:
+        nx.set_node_attributes(example_1, 1, NWL)
+        wtu = NWL
+    else:
+        wtu = None
+
+    # partitioning
+    clusters_1 = {
+        frozenset(x)
+        for x in nx.community.lukes_partitioning(
+            example_1, limit, node_weight=wtu, edge_weight=EWL
+        )
+    }
+
+    return clusters_1
+
+
+# second test from the Lukes original paper
+def paper_2_case(explicit_edge_wt=True, directed=False):
+    # problem specific constants
+    byte_block_size = 32
+
+    # configuration
+    if directed:
+        example_2 = nx.DiGraph()
+    else:
+        example_2 = nx.Graph()
+
+    if explicit_edge_wt:
+        edic = {EWL: 1}
+        wtu = EWL
+    else:
+        edic = {}
+        wtu = None
+
+    # graph creation
+    example_2.add_edge("name", "home_address", **edic)
+    example_2.add_edge("name", "education", **edic)
+    example_2.add_edge("education", "bs", **edic)
+    example_2.add_edge("education", "ms", **edic)
+    example_2.add_edge("education", "phd", **edic)
+    example_2.add_edge("name", "telephone", **edic)
+    example_2.add_edge("telephone", "home", **edic)
+    example_2.add_edge("telephone", "office", **edic)
+    example_2.add_edge("office", "no1", **edic)
+    example_2.add_edge("office", "no2", **edic)
+
+    example_2.nodes["name"][NWL] = 20
+    example_2.nodes["education"][NWL] = 10
+    example_2.nodes["bs"][NWL] = 1
+    example_2.nodes["ms"][NWL] = 1
+    example_2.nodes["phd"][NWL] = 1
+    example_2.nodes["home_address"][NWL] = 8
+    example_2.nodes["telephone"][NWL] = 8
+    example_2.nodes["home"][NWL] = 8
+    example_2.nodes["office"][NWL] = 4
+    example_2.nodes["no1"][NWL] = 1
+    example_2.nodes["no2"][NWL] = 1
+
+    # partitioning
+    clusters_2 = {
+        frozenset(x)
+        for x in nx.community.lukes_partitioning(
+            example_2, byte_block_size, node_weight=NWL, edge_weight=wtu
+        )
+    }
+
+    return clusters_2
+
+
+def test_paper_1_case():
+    ground_truth = {frozenset([1, 4]), frozenset([2, 3, 5])}
+
+    tf = (True, False)
+    for flt, nwt, drc in product(tf, tf, tf):
+        part = paper_1_case(flt, nwt, drc)
+        assert part == ground_truth
+
+
+def test_paper_2_case():
+    ground_truth = {
+        frozenset(["education", "bs", "ms", "phd"]),
+        frozenset(["name", "home_address"]),
+        frozenset(["telephone", "home", "office", "no1", "no2"]),
+    }
+
+    tf = (True, False)
+    for ewt, drc in product(tf, tf):
+        part = paper_2_case(ewt, drc)
+        assert part == ground_truth
+
+
+def test_mandatory_tree():
+    not_a_tree = nx.complete_graph(4)
+
+    with pytest.raises(nx.NotATree):
+        nx.community.lukes_partitioning(not_a_tree, 5)
+
+
+def test_mandatory_integrality():
+    byte_block_size = 32
+
+    ex_1_broken = nx.DiGraph()
+
+    ex_1_broken.add_edge(1, 2, **{EWL: 3.2})
+    ex_1_broken.add_edge(1, 4, **{EWL: 2.4})
+    ex_1_broken.add_edge(2, 3, **{EWL: 4.0})
+    ex_1_broken.add_edge(2, 5, **{EWL: 6.3})
+
+    ex_1_broken.nodes[1][NWL] = 1.2  # !
+    ex_1_broken.nodes[2][NWL] = 1
+    ex_1_broken.nodes[3][NWL] = 1
+    ex_1_broken.nodes[4][NWL] = 1
+    ex_1_broken.nodes[5][NWL] = 2
+
+    with pytest.raises(TypeError):
+        nx.community.lukes_partitioning(
+            ex_1_broken, byte_block_size, node_weight=NWL, edge_weight=EWL
+        )
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_modularity_max.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_modularity_max.py
new file mode 100644
index 00000000..0121367f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_modularity_max.py
@@ -0,0 +1,340 @@
+import pytest
+
+import networkx as nx
+from networkx.algorithms.community import (
+    greedy_modularity_communities,
+    naive_greedy_modularity_communities,
+)
+
+
+@pytest.mark.parametrize(
+    "func", (greedy_modularity_communities, naive_greedy_modularity_communities)
+)
+def test_modularity_communities(func):
+    G = nx.karate_club_graph()
+    john_a = frozenset(
+        [8, 14, 15, 18, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]
+    )
+    mr_hi = frozenset([0, 4, 5, 6, 10, 11, 16, 19])
+    overlap = frozenset([1, 2, 3, 7, 9, 12, 13, 17, 21])
+    expected = {john_a, overlap, mr_hi}
+    assert set(func(G, weight=None)) == expected
+
+
+@pytest.mark.parametrize(
+    "func", (greedy_modularity_communities, naive_greedy_modularity_communities)
+)
+def test_modularity_communities_categorical_labels(func):
+    # Using other than 0-starting contiguous integers as node-labels.
+    G = nx.Graph(
+        [
+            ("a", "b"),
+            ("a", "c"),
+            ("b", "c"),
+            ("b", "d"),  # inter-community edge
+            ("d", "e"),
+            ("d", "f"),
+            ("d", "g"),
+            ("f", "g"),
+            ("d", "e"),
+            ("f", "e"),
+        ]
+    )
+    expected = {frozenset({"f", "g", "e", "d"}), frozenset({"a", "b", "c"})}
+    assert set(func(G)) == expected
+
+
+def test_greedy_modularity_communities_components():
+    # Test for gh-5530
+    G = nx.Graph([(0, 1), (2, 3), (4, 5), (5, 6)])
+    # usual case with 3 components
+    assert greedy_modularity_communities(G) == [{4, 5, 6}, {0, 1}, {2, 3}]
+    # best_n can make the algorithm continue even when modularity goes down
+    assert greedy_modularity_communities(G, best_n=3) == [{4, 5, 6}, {0, 1}, {2, 3}]
+    assert greedy_modularity_communities(G, best_n=2) == [{0, 1, 4, 5, 6}, {2, 3}]
+    assert greedy_modularity_communities(G, best_n=1) == [{0, 1, 2, 3, 4, 5, 6}]
+
+
+def test_greedy_modularity_communities_relabeled():
+    # Test for gh-4966
+    G = nx.balanced_tree(2, 2)
+    mapping = {0: "a", 1: "b", 2: "c", 3: "d", 4: "e", 5: "f", 6: "g", 7: "h"}
+    G = nx.relabel_nodes(G, mapping)
+    expected = [frozenset({"e", "d", "a", "b"}), frozenset({"c", "f", "g"})]
+    assert greedy_modularity_communities(G) == expected
+
+
+def test_greedy_modularity_communities_directed():
+    G = nx.DiGraph(
+        [
+            ("a", "b"),
+            ("a", "c"),
+            ("b", "c"),
+            ("b", "d"),  # inter-community edge
+            ("d", "e"),
+            ("d", "f"),
+            ("d", "g"),
+            ("f", "g"),
+            ("d", "e"),
+            ("f", "e"),
+        ]
+    )
+    expected = [frozenset({"f", "g", "e", "d"}), frozenset({"a", "b", "c"})]
+    assert greedy_modularity_communities(G) == expected
+
+    # with loops
+    G = nx.DiGraph()
+    G.add_edges_from(
+        [(1, 1), (1, 2), (1, 3), (2, 3), (1, 4), (4, 4), (5, 5), (4, 5), (4, 6), (5, 6)]
+    )
+    expected = [frozenset({1, 2, 3}), frozenset({4, 5, 6})]
+    assert greedy_modularity_communities(G) == expected
+
+
+@pytest.mark.parametrize(
+    "func", (greedy_modularity_communities, naive_greedy_modularity_communities)
+)
+def test_modularity_communities_weighted(func):
+    G = nx.balanced_tree(2, 3)
+    for a, b in G.edges:
+        if ((a == 1) or (a == 2)) and (b != 0):
+            G[a][b]["weight"] = 10.0
+        else:
+            G[a][b]["weight"] = 1.0
+
+    expected = [{0, 1, 3, 4, 7, 8, 9, 10}, {2, 5, 6, 11, 12, 13, 14}]
+
+    assert func(G, weight="weight") == expected
+    assert func(G, weight="weight", resolution=0.9) == expected
+    assert func(G, weight="weight", resolution=0.3) == expected
+    assert func(G, weight="weight", resolution=1.1) != expected
+
+
+def test_modularity_communities_floating_point():
+    # check for floating point error when used as key in the mapped_queue dict.
+    # Test for gh-4992 and gh-5000
+    G = nx.Graph()
+    G.add_weighted_edges_from(
+        [(0, 1, 12), (1, 4, 71), (2, 3, 15), (2, 4, 10), (3, 6, 13)]
+    )
+    expected = [{0, 1, 4}, {2, 3, 6}]
+    assert greedy_modularity_communities(G, weight="weight") == expected
+    assert (
+        greedy_modularity_communities(G, weight="weight", resolution=0.99) == expected
+    )
+
+
+def test_modularity_communities_directed_weighted():
+    G = nx.DiGraph()
+    G.add_weighted_edges_from(
+        [
+            (1, 2, 5),
+            (1, 3, 3),
+            (2, 3, 6),
+            (2, 6, 1),
+            (1, 4, 1),
+            (4, 5, 3),
+            (4, 6, 7),
+            (5, 6, 2),
+            (5, 7, 5),
+            (5, 8, 4),
+            (6, 8, 3),
+        ]
+    )
+    expected = [frozenset({4, 5, 6, 7, 8}), frozenset({1, 2, 3})]
+    assert greedy_modularity_communities(G, weight="weight") == expected
+
+    # A large weight of the edge (2, 6) causes 6 to change group, even if it shares
+    # only one connection with the new group and 3 with the old one.
+    G[2][6]["weight"] = 20
+    expected = [frozenset({1, 2, 3, 6}), frozenset({4, 5, 7, 8})]
+    assert greedy_modularity_communities(G, weight="weight") == expected
+
+
+def test_greedy_modularity_communities_multigraph():
+    G = nx.MultiGraph()
+    G.add_edges_from(
+        [
+            (1, 2),
+            (1, 2),
+            (1, 3),
+            (2, 3),
+            (1, 4),
+            (2, 4),
+            (4, 5),
+            (5, 6),
+            (5, 7),
+            (5, 7),
+            (6, 7),
+            (7, 8),
+            (5, 8),
+        ]
+    )
+    expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})]
+    assert greedy_modularity_communities(G) == expected
+
+    # Converting (4, 5) into a multi-edge causes node 4 to change group.
+    G.add_edge(4, 5)
+    expected = [frozenset({4, 5, 6, 7, 8}), frozenset({1, 2, 3})]
+    assert greedy_modularity_communities(G) == expected
+
+
+def test_greedy_modularity_communities_multigraph_weighted():
+    G = nx.MultiGraph()
+    G.add_weighted_edges_from(
+        [
+            (1, 2, 5),
+            (1, 2, 3),
+            (1, 3, 6),
+            (1, 3, 6),
+            (2, 3, 4),
+            (1, 4, 1),
+            (1, 4, 1),
+            (2, 4, 3),
+            (2, 4, 3),
+            (4, 5, 1),
+            (5, 6, 3),
+            (5, 6, 7),
+            (5, 6, 4),
+            (5, 7, 9),
+            (5, 7, 9),
+            (6, 7, 8),
+            (7, 8, 2),
+            (7, 8, 2),
+            (5, 8, 6),
+            (5, 8, 6),
+        ]
+    )
+    expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})]
+    assert greedy_modularity_communities(G, weight="weight") == expected
+
+    # Adding multi-edge (4, 5, 16) causes node 4 to change group.
+    G.add_edge(4, 5, weight=16)
+    expected = [frozenset({4, 5, 6, 7, 8}), frozenset({1, 2, 3})]
+    assert greedy_modularity_communities(G, weight="weight") == expected
+
+    # Increasing the weight of edge (1, 4) causes node 4 to return to the former group.
+    G[1][4][1]["weight"] = 3
+    expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})]
+    assert greedy_modularity_communities(G, weight="weight") == expected
+
+
+def test_greed_modularity_communities_multidigraph():
+    G = nx.MultiDiGraph()
+    G.add_edges_from(
+        [
+            (1, 2),
+            (1, 2),
+            (3, 1),
+            (2, 3),
+            (2, 3),
+            (3, 2),
+            (1, 4),
+            (2, 4),
+            (4, 2),
+            (4, 5),
+            (5, 6),
+            (5, 6),
+            (6, 5),
+            (5, 7),
+            (6, 7),
+            (7, 8),
+            (5, 8),
+            (8, 4),
+        ]
+    )
+    expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})]
+    assert greedy_modularity_communities(G, weight="weight") == expected
+
+
+def test_greed_modularity_communities_multidigraph_weighted():
+    G = nx.MultiDiGraph()
+    G.add_weighted_edges_from(
+        [
+            (1, 2, 5),
+            (1, 2, 3),
+            (3, 1, 6),
+            (1, 3, 6),
+            (3, 2, 4),
+            (1, 4, 2),
+            (1, 4, 5),
+            (2, 4, 3),
+            (3, 2, 8),
+            (4, 2, 3),
+            (4, 3, 5),
+            (4, 5, 2),
+            (5, 6, 3),
+            (5, 6, 7),
+            (6, 5, 4),
+            (5, 7, 9),
+            (5, 7, 9),
+            (7, 6, 8),
+            (7, 8, 2),
+            (8, 7, 2),
+            (5, 8, 6),
+            (5, 8, 6),
+        ]
+    )
+    expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})]
+    assert greedy_modularity_communities(G, weight="weight") == expected
+
+
+def test_resolution_parameter_impact():
+    G = nx.barbell_graph(5, 3)
+
+    gamma = 1
+    expected = [frozenset(range(5)), frozenset(range(8, 13)), frozenset(range(5, 8))]
+    assert greedy_modularity_communities(G, resolution=gamma) == expected
+    assert naive_greedy_modularity_communities(G, resolution=gamma) == expected
+
+    gamma = 2.5
+    expected = [{0, 1, 2, 3}, {9, 10, 11, 12}, {5, 6, 7}, {4}, {8}]
+    assert greedy_modularity_communities(G, resolution=gamma) == expected
+    assert naive_greedy_modularity_communities(G, resolution=gamma) == expected
+
+    gamma = 0.3
+    expected = [frozenset(range(8)), frozenset(range(8, 13))]
+    assert greedy_modularity_communities(G, resolution=gamma) == expected
+    assert naive_greedy_modularity_communities(G, resolution=gamma) == expected
+
+
+def test_cutoff_parameter():
+    G = nx.circular_ladder_graph(4)
+
+    # No aggregation:
+    expected = [{k} for k in range(8)]
+    assert greedy_modularity_communities(G, cutoff=8) == expected
+
+    # Aggregation to half order (number of nodes)
+    expected = [{k, k + 1} for k in range(0, 8, 2)]
+    assert greedy_modularity_communities(G, cutoff=4) == expected
+
+    # Default aggregation case (here, 2 communities emerge)
+    expected = [frozenset(range(4)), frozenset(range(4, 8))]
+    assert greedy_modularity_communities(G, cutoff=1) == expected
+
+
+def test_best_n():
+    G = nx.barbell_graph(5, 3)
+
+    # Same result as without enforcing cutoff:
+    best_n = 3
+    expected = [frozenset(range(5)), frozenset(range(8, 13)), frozenset(range(5, 8))]
+    assert greedy_modularity_communities(G, best_n=best_n) == expected
+
+    # One additional merging step:
+    best_n = 2
+    expected = [frozenset(range(8)), frozenset(range(8, 13))]
+    assert greedy_modularity_communities(G, best_n=best_n) == expected
+
+    # Two additional merging steps:
+    best_n = 1
+    expected = [frozenset(range(13))]
+    assert greedy_modularity_communities(G, best_n=best_n) == expected
+
+
+def test_greedy_modularity_communities_corner_cases():
+    G = nx.empty_graph()
+    assert nx.community.greedy_modularity_communities(G) == []
+    G.add_nodes_from(range(3))
+    assert nx.community.greedy_modularity_communities(G) == [{0}, {1}, {2}]
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_quality.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_quality.py
new file mode 100644
index 00000000..c502c7e3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_quality.py
@@ -0,0 +1,139 @@
+"""Unit tests for the :mod:`networkx.algorithms.community.quality`
+module.
+
+"""
+
+import pytest
+
+import networkx as nx
+from networkx import barbell_graph
+from networkx.algorithms.community import modularity, partition_quality
+from networkx.algorithms.community.quality import inter_community_edges
+
+
+class TestPerformance:
+    """Unit tests for the :func:`performance` function."""
+
+    def test_bad_partition(self):
+        """Tests that a poor partition has a low performance measure."""
+        G = barbell_graph(3, 0)
+        partition = [{0, 1, 4}, {2, 3, 5}]
+        assert 8 / 15 == pytest.approx(partition_quality(G, partition)[1], abs=1e-7)
+
+    def test_good_partition(self):
+        """Tests that a good partition has a high performance measure."""
+        G = barbell_graph(3, 0)
+        partition = [{0, 1, 2}, {3, 4, 5}]
+        assert 14 / 15 == pytest.approx(partition_quality(G, partition)[1], abs=1e-7)
+
+
+class TestCoverage:
+    """Unit tests for the :func:`coverage` function."""
+
+    def test_bad_partition(self):
+        """Tests that a poor partition has a low coverage measure."""
+        G = barbell_graph(3, 0)
+        partition = [{0, 1, 4}, {2, 3, 5}]
+        assert 3 / 7 == pytest.approx(partition_quality(G, partition)[0], abs=1e-7)
+
+    def test_good_partition(self):
+        """Tests that a good partition has a high coverage measure."""
+        G = barbell_graph(3, 0)
+        partition = [{0, 1, 2}, {3, 4, 5}]
+        assert 6 / 7 == pytest.approx(partition_quality(G, partition)[0], abs=1e-7)
+
+
+def test_modularity():
+    G = nx.barbell_graph(3, 0)
+    C = [{0, 1, 4}, {2, 3, 5}]
+    assert (-16 / (14**2)) == pytest.approx(modularity(G, C), abs=1e-7)
+    C = [{0, 1, 2}, {3, 4, 5}]
+    assert (35 * 2) / (14**2) == pytest.approx(modularity(G, C), abs=1e-7)
+
+    n = 1000
+    G = nx.erdos_renyi_graph(n, 0.09, seed=42, directed=True)
+    C = [set(range(n // 2)), set(range(n // 2, n))]
+    assert 0.00017154251389292754 == pytest.approx(modularity(G, C), abs=1e-7)
+
+    G = nx.margulis_gabber_galil_graph(10)
+    mid_value = G.number_of_nodes() // 2
+    nodes = list(G.nodes)
+    C = [set(nodes[:mid_value]), set(nodes[mid_value:])]
+    assert 0.13 == pytest.approx(modularity(G, C), abs=1e-7)
+
+    G = nx.DiGraph()
+    G.add_edges_from([(2, 1), (2, 3), (3, 4)])
+    C = [{1, 2}, {3, 4}]
+    assert 2 / 9 == pytest.approx(modularity(G, C), abs=1e-7)
+
+
+def test_modularity_resolution():
+    G = nx.barbell_graph(3, 0)
+    C = [{0, 1, 4}, {2, 3, 5}]
+    assert modularity(G, C) == pytest.approx(3 / 7 - 100 / 14**2)
+    gamma = 2
+    result = modularity(G, C, resolution=gamma)
+    assert result == pytest.approx(3 / 7 - gamma * 100 / 14**2)
+    gamma = 0.2
+    result = modularity(G, C, resolution=gamma)
+    assert result == pytest.approx(3 / 7 - gamma * 100 / 14**2)
+
+    C = [{0, 1, 2}, {3, 4, 5}]
+    assert modularity(G, C) == pytest.approx(6 / 7 - 98 / 14**2)
+    gamma = 2
+    result = modularity(G, C, resolution=gamma)
+    assert result == pytest.approx(6 / 7 - gamma * 98 / 14**2)
+    gamma = 0.2
+    result = modularity(G, C, resolution=gamma)
+    assert result == pytest.approx(6 / 7 - gamma * 98 / 14**2)
+
+    G = nx.barbell_graph(5, 3)
+    C = [frozenset(range(5)), frozenset(range(8, 13)), frozenset(range(5, 8))]
+    gamma = 1
+    result = modularity(G, C, resolution=gamma)
+    # This C is maximal for gamma=1:  modularity = 0.518229
+    assert result == pytest.approx((22 / 24) - gamma * (918 / (48**2)))
+    gamma = 2
+    result = modularity(G, C, resolution=gamma)
+    assert result == pytest.approx((22 / 24) - gamma * (918 / (48**2)))
+    gamma = 0.2
+    result = modularity(G, C, resolution=gamma)
+    assert result == pytest.approx((22 / 24) - gamma * (918 / (48**2)))
+
+    C = [{0, 1, 2, 3}, {9, 10, 11, 12}, {5, 6, 7}, {4}, {8}]
+    gamma = 1
+    result = modularity(G, C, resolution=gamma)
+    assert result == pytest.approx((14 / 24) - gamma * (598 / (48**2)))
+    gamma = 2.5
+    result = modularity(G, C, resolution=gamma)
+    # This C is maximal for gamma=2.5:  modularity = -0.06553819
+    assert result == pytest.approx((14 / 24) - gamma * (598 / (48**2)))
+    gamma = 0.2
+    result = modularity(G, C, resolution=gamma)
+    assert result == pytest.approx((14 / 24) - gamma * (598 / (48**2)))
+
+    C = [frozenset(range(8)), frozenset(range(8, 13))]
+    gamma = 1
+    result = modularity(G, C, resolution=gamma)
+    assert result == pytest.approx((23 / 24) - gamma * (1170 / (48**2)))
+    gamma = 2
+    result = modularity(G, C, resolution=gamma)
+    assert result == pytest.approx((23 / 24) - gamma * (1170 / (48**2)))
+    gamma = 0.3
+    result = modularity(G, C, resolution=gamma)
+    # This C is maximal for gamma=0.3:  modularity = 0.805990
+    assert result == pytest.approx((23 / 24) - gamma * (1170 / (48**2)))
+
+
+def test_inter_community_edges_with_digraphs():
+    G = nx.complete_graph(2, create_using=nx.DiGraph())
+    partition = [{0}, {1}]
+    assert inter_community_edges(G, partition) == 2
+
+    G = nx.complete_graph(10, create_using=nx.DiGraph())
+    partition = [{0}, {1, 2}, {3, 4, 5}, {6, 7, 8, 9}]
+    assert inter_community_edges(G, partition) == 70
+
+    G = nx.cycle_graph(4, create_using=nx.DiGraph())
+    partition = [{0, 1}, {2, 3}]
+    assert inter_community_edges(G, partition) == 2
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_utils.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_utils.py
new file mode 100644
index 00000000..ea019db9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_utils.py
@@ -0,0 +1,26 @@
+"""Unit tests for the :mod:`networkx.algorithms.community.utils` module."""
+
+import networkx as nx
+
+
+def test_is_partition():
+    G = nx.empty_graph(3)
+    assert nx.community.is_partition(G, [{0, 1}, {2}])
+    assert nx.community.is_partition(G, ({0, 1}, {2}))
+    assert nx.community.is_partition(G, ([0, 1], [2]))
+    assert nx.community.is_partition(G, [[0, 1], [2]])
+
+
+def test_not_covering():
+    G = nx.empty_graph(3)
+    assert not nx.community.is_partition(G, [{0}, {1}])
+
+
+def test_not_disjoint():
+    G = nx.empty_graph(3)
+    assert not nx.community.is_partition(G, [{0, 1}, {1, 2}])
+
+
+def test_not_node():
+    G = nx.empty_graph(3)
+    assert not nx.community.is_partition(G, [{0, 1}, {3}])