Skip to content

Commit cd6516b

Browse files
sprotasovitskyclaude
authored andcommitted
fix: add max iteration cap to label_propagation to prevent infinite loop
The synchronous label propagation algorithm can oscillate indefinitely on bipartite or near-bipartite subgraphs, causing build_communities() to hang with 100% CPU usage and no indication of failure. Add a configurable max_iterations parameter (default: 30) to guarantee termination. In practice, label propagation converges within 5-15 iterations on real-world graphs — if it hasn't converged by 30, it is oscillating and the current approximation is returned. Also adds debug/warning logging for convergence status. Fixes #402 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9d509a2 commit cd6516b

File tree

1 file changed

+14
-3
lines changed

1 file changed

+14
-3
lines changed

graphiti_core/utils/maintenance/community_operations.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,21 @@ async def get_community_clusters(
8989
return community_clusters
9090

9191

92-
def label_propagation(projection: dict[str, list[Neighbor]]) -> list[list[str]]:
92+
def label_propagation(
93+
projection: dict[str, list[Neighbor]], max_iterations: int = 30
94+
) -> list[list[str]]:
9395
# Implement the label propagation community detection algorithm.
9496
# 1. Start with each node being assigned its own community
9597
# 2. Each node will take on the community of the plurality of its neighbors
9698
# 3. Ties are broken by going to the largest community
97-
# 4. Continue until no communities change during propagation
99+
# 4. Continue until no communities change during propagation or max_iterations is reached
100+
#
101+
# Synchronous label propagation can oscillate on bipartite or near-bipartite
102+
# subgraphs, so a max iteration cap is required to guarantee termination.
98103

99104
community_map = {uuid: i for i, uuid in enumerate(projection.keys())}
100105

101-
while True:
106+
for iteration in range(max_iterations):
102107
no_change = True
103108
new_community_map: dict[str, int] = {}
104109

@@ -125,9 +130,15 @@ def label_propagation(projection: dict[str, list[Neighbor]]) -> list[list[str]]:
125130
no_change = False
126131

127132
if no_change:
133+
logger.debug(f'Label propagation converged after {iteration + 1} iterations')
128134
break
129135

130136
community_map = new_community_map
137+
else:
138+
logger.warning(
139+
f'Label propagation did not converge after {max_iterations} iterations, '
140+
f'returning best approximation'
141+
)
131142

132143
community_cluster_map = defaultdict(list)
133144
for uuid, community in community_map.items():

0 commit comments

Comments
 (0)