Perform clustering on ideas in the graph and assign cluster IDs. Args: method (str): Clustering method to use. Options: - "louvain": Community detection using Louvain algorithm (default) - "spectral": Spectral clustering -
(self, method: str = "louvain")
| 236 | return [] |
| 237 | |
| 238 | def cluster_ideas(self, method: str = "louvain") -> None: |
| 239 | """ |
| 240 | Perform clustering on ideas in the graph and assign cluster IDs. |
| 241 | |
| 242 | Args: |
| 243 | method (str): Clustering method to use. Options: |
| 244 | - "louvain": Community detection using Louvain algorithm (default) |
| 245 | - "spectral": Spectral clustering |
| 246 | - "embedding": Clustering based on embeddings (requires FINCH) |
| 247 | """ |
| 248 | nodes = list(self.graph.nodes) |
| 249 | |
| 250 | if len(nodes) == 0: |
| 251 | logger.warning("No nodes in graph, skipping clustering") |
| 252 | return |
| 253 | |
| 254 | logger.info(f"Clustering {len(nodes)} ideas using method: {method}") |
| 255 | |
| 256 | if method == "louvain": |
| 257 | # Use Louvain community detection |
| 258 | try: |
| 259 | communities = nx.community.louvain_communities(self.graph, seed=42) |
| 260 | for cluster_id, community in enumerate(communities): |
| 261 | for node_id in community: |
| 262 | self.graph.nodes[node_id]['cluster_id'] = cluster_id |
| 263 | logger.info(f"Louvain clustering created {len(communities)} clusters") |
| 264 | except Exception as e: |
| 265 | logger.error(f"Louvain clustering failed: {e}") |
| 266 | # Fallback: assign all to cluster 0 |
| 267 | for node_id in nodes: |
| 268 | self.graph.nodes[node_id]['cluster_id'] = 0 |
| 269 | |
| 270 | elif method == "spectral": |
| 271 | # Use spectral clustering (requires connected components) |
| 272 | try: |
| 273 | # Get largest connected component |
| 274 | if nx.is_connected(self.graph): |
| 275 | adj_matrix = nx.to_numpy_array(self.graph) |
| 276 | from sklearn.cluster import SpectralClustering |
| 277 | n_clusters = min(5, len(nodes)) |
| 278 | clustering = SpectralClustering(n_clusters=n_clusters, affinity='precomputed') |
| 279 | labels = clustering.fit_predict(adj_matrix) |
| 280 | for node_id, label in zip(nodes, labels): |
| 281 | self.graph.nodes[node_id]['cluster_id'] = int(label) |
| 282 | logger.info(f"Spectral clustering created {n_clusters} clusters") |
| 283 | else: |
| 284 | logger.warning("Graph not connected, using component-based clustering") |
| 285 | for cluster_id, component in enumerate(nx.connected_components(self.graph)): |
| 286 | for node_id in component: |
| 287 | self.graph.nodes[node_id]['cluster_id'] = cluster_id |
| 288 | except Exception as e: |
| 289 | logger.error(f"Spectral clustering failed: {e}") |
| 290 | for node_id in nodes: |
| 291 | self.graph.nodes[node_id]['cluster_id'] = 0 |
| 292 | |
| 293 | elif method == "embedding": |
| 294 | # Use embedding-based clustering with FINCH or K-means |
| 295 | if not CHROMA_AVAILABLE or self.collection is None: |
no test coverage detected