aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/community/tests
diff options
context:
space:
mode:
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}])