@graphty/algorithms 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/README.md +827 -60
  2. package/dist/algorithms.d.ts +2 -0
  3. package/dist/algorithms.d.ts.map +1 -0
  4. package/dist/algorithms.js +2 -0
  5. package/dist/algorithms.js.map +1 -0
  6. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
  7. package/dist/src/algorithms/centrality/betweenness.js +22 -7
  8. package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
  9. package/dist/src/algorithms/centrality/closeness.js +7 -7
  10. package/dist/src/algorithms/centrality/closeness.js.map +1 -1
  11. package/dist/src/algorithms/centrality/degree.js +1 -1
  12. package/dist/src/algorithms/centrality/degree.js.map +1 -1
  13. package/dist/src/algorithms/centrality/eigenvector.d.ts +27 -0
  14. package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -0
  15. package/dist/src/algorithms/centrality/eigenvector.js +113 -0
  16. package/dist/src/algorithms/centrality/eigenvector.js.map +1 -0
  17. package/dist/src/algorithms/centrality/hits.d.ts +35 -0
  18. package/dist/src/algorithms/centrality/hits.d.ts.map +1 -0
  19. package/dist/src/algorithms/centrality/hits.js +146 -0
  20. package/dist/src/algorithms/centrality/hits.js.map +1 -0
  21. package/dist/src/algorithms/centrality/index.d.ts +6 -0
  22. package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
  23. package/dist/src/algorithms/centrality/index.js +3 -0
  24. package/dist/src/algorithms/centrality/index.js.map +1 -1
  25. package/dist/src/algorithms/centrality/katz.d.ts +30 -0
  26. package/dist/src/algorithms/centrality/katz.d.ts.map +1 -0
  27. package/dist/src/algorithms/centrality/katz.js +85 -0
  28. package/dist/src/algorithms/centrality/katz.js.map +1 -0
  29. package/dist/src/algorithms/centrality/pagerank.js +3 -3
  30. package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
  31. package/dist/src/algorithms/community/girvan-newman.d.ts +23 -0
  32. package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -0
  33. package/dist/src/algorithms/community/girvan-newman.js +297 -0
  34. package/dist/src/algorithms/community/girvan-newman.js.map +1 -0
  35. package/dist/src/algorithms/community/index.d.ts +12 -0
  36. package/dist/src/algorithms/community/index.d.ts.map +1 -0
  37. package/dist/src/algorithms/community/index.js +12 -0
  38. package/dist/src/algorithms/community/index.js.map +1 -0
  39. package/dist/src/algorithms/community/label-propagation.d.ts +42 -0
  40. package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -0
  41. package/dist/src/algorithms/community/label-propagation.js +307 -0
  42. package/dist/src/algorithms/community/label-propagation.js.map +1 -0
  43. package/dist/src/algorithms/community/leiden.d.ts +33 -0
  44. package/dist/src/algorithms/community/leiden.d.ts.map +1 -0
  45. package/dist/src/algorithms/community/leiden.js +434 -0
  46. package/dist/src/algorithms/community/leiden.js.map +1 -0
  47. package/dist/src/algorithms/community/louvain.d.ts +19 -0
  48. package/dist/src/algorithms/community/louvain.d.ts.map +1 -0
  49. package/dist/src/algorithms/community/louvain.js +219 -0
  50. package/dist/src/algorithms/community/louvain.js.map +1 -0
  51. package/dist/src/algorithms/components/connected.js +7 -7
  52. package/dist/src/algorithms/components/connected.js.map +1 -1
  53. package/dist/src/algorithms/index.d.ts +7 -0
  54. package/dist/src/algorithms/index.d.ts.map +1 -1
  55. package/dist/src/algorithms/index.js +14 -0
  56. package/dist/src/algorithms/index.js.map +1 -1
  57. package/dist/src/algorithms/matching/bipartite.d.ts +37 -0
  58. package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -0
  59. package/dist/src/algorithms/matching/bipartite.js +127 -0
  60. package/dist/src/algorithms/matching/bipartite.js.map +1 -0
  61. package/dist/src/algorithms/matching/index.d.ts +8 -0
  62. package/dist/src/algorithms/matching/index.d.ts.map +1 -0
  63. package/dist/src/algorithms/matching/index.js +6 -0
  64. package/dist/src/algorithms/matching/index.js.map +1 -0
  65. package/dist/src/algorithms/matching/isomorphism.d.ts +32 -0
  66. package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -0
  67. package/dist/src/algorithms/matching/isomorphism.js +291 -0
  68. package/dist/src/algorithms/matching/isomorphism.js.map +1 -0
  69. package/dist/src/algorithms/mst/index.d.ts +4 -0
  70. package/dist/src/algorithms/mst/index.d.ts.map +1 -0
  71. package/dist/src/algorithms/mst/index.js +3 -0
  72. package/dist/src/algorithms/mst/index.js.map +1 -0
  73. package/dist/src/algorithms/mst/kruskal.d.ts +9 -0
  74. package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -0
  75. package/dist/src/algorithms/mst/kruskal.js +43 -0
  76. package/dist/src/algorithms/mst/kruskal.js.map +1 -0
  77. package/dist/src/algorithms/mst/prim.d.ts +5 -0
  78. package/dist/src/algorithms/mst/prim.d.ts.map +1 -0
  79. package/dist/src/algorithms/mst/prim.js +63 -0
  80. package/dist/src/algorithms/mst/prim.js.map +1 -0
  81. package/dist/src/algorithms/shortest-path/bellman-ford.js +2 -2
  82. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
  83. package/dist/src/algorithms/shortest-path/dijkstra.js +5 -5
  84. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
  85. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +14 -0
  86. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -0
  87. package/dist/src/algorithms/shortest-path/floyd-warshall.js +129 -0
  88. package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -0
  89. package/dist/src/algorithms/shortest-path/index.d.ts +2 -0
  90. package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
  91. package/dist/src/algorithms/shortest-path/index.js +1 -0
  92. package/dist/src/algorithms/shortest-path/index.js.map +1 -1
  93. package/dist/src/algorithms/traversal/bfs.js +2 -2
  94. package/dist/src/algorithms/traversal/bfs.js.map +1 -1
  95. package/dist/src/algorithms/traversal/dfs.js +11 -11
  96. package/dist/src/algorithms/traversal/dfs.js.map +1 -1
  97. package/dist/src/clustering/hierarchical.d.ts +48 -0
  98. package/dist/src/clustering/hierarchical.d.ts.map +1 -0
  99. package/dist/src/clustering/hierarchical.js +439 -0
  100. package/dist/src/clustering/hierarchical.js.map +1 -0
  101. package/dist/src/clustering/index.d.ts +7 -0
  102. package/dist/src/clustering/index.d.ts.map +1 -0
  103. package/dist/src/clustering/index.js +5 -0
  104. package/dist/src/clustering/index.js.map +1 -0
  105. package/dist/src/clustering/k-core.d.ts +63 -0
  106. package/dist/src/clustering/k-core.d.ts.map +1 -0
  107. package/dist/src/clustering/k-core.js +377 -0
  108. package/dist/src/clustering/k-core.js.map +1 -0
  109. package/dist/src/clustering/mcl.d.ts +36 -0
  110. package/dist/src/clustering/mcl.d.ts.map +1 -0
  111. package/dist/src/clustering/mcl.js +343 -0
  112. package/dist/src/clustering/mcl.js.map +1 -0
  113. package/dist/src/clustering/spectral.d.ts +28 -0
  114. package/dist/src/clustering/spectral.d.ts.map +1 -0
  115. package/dist/src/clustering/spectral.js +447 -0
  116. package/dist/src/clustering/spectral.js.map +1 -0
  117. package/dist/src/core/graph.d.ts +9 -0
  118. package/dist/src/core/graph.d.ts.map +1 -1
  119. package/dist/src/core/graph.js +31 -11
  120. package/dist/src/core/graph.js.map +1 -1
  121. package/dist/src/data-structures/priority-queue.js +0 -2
  122. package/dist/src/data-structures/priority-queue.js.map +1 -1
  123. package/dist/src/data-structures/union-find.js +2 -5
  124. package/dist/src/data-structures/union-find.js.map +1 -1
  125. package/dist/src/flow/ford-fulkerson.d.ts +53 -0
  126. package/dist/src/flow/ford-fulkerson.d.ts.map +1 -0
  127. package/dist/src/flow/ford-fulkerson.js +355 -0
  128. package/dist/src/flow/ford-fulkerson.js.map +1 -0
  129. package/dist/src/flow/index.d.ts +3 -0
  130. package/dist/src/flow/index.d.ts.map +1 -0
  131. package/dist/src/flow/index.js +3 -0
  132. package/dist/src/flow/index.js.map +1 -0
  133. package/dist/src/flow/min-cut.d.ts +49 -0
  134. package/dist/src/flow/min-cut.d.ts.map +1 -0
  135. package/dist/src/flow/min-cut.js +383 -0
  136. package/dist/src/flow/min-cut.js.map +1 -0
  137. package/dist/src/index.d.ts +1 -1
  138. package/dist/src/index.d.ts.map +1 -1
  139. package/dist/src/index.js.map +1 -1
  140. package/dist/src/link-prediction/adamic-adar.d.ts +56 -0
  141. package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -0
  142. package/dist/src/link-prediction/adamic-adar.js +256 -0
  143. package/dist/src/link-prediction/adamic-adar.js.map +1 -0
  144. package/dist/src/link-prediction/common-neighbors.d.ts +50 -0
  145. package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -0
  146. package/dist/src/link-prediction/common-neighbors.js +155 -0
  147. package/dist/src/link-prediction/common-neighbors.js.map +1 -0
  148. package/dist/src/link-prediction/index.d.ts +7 -0
  149. package/dist/src/link-prediction/index.d.ts.map +1 -0
  150. package/dist/src/link-prediction/index.js +6 -0
  151. package/dist/src/link-prediction/index.js.map +1 -0
  152. package/dist/src/pathfinding/astar.d.ts +50 -0
  153. package/dist/src/pathfinding/astar.d.ts.map +1 -0
  154. package/dist/src/pathfinding/astar.js +180 -0
  155. package/dist/src/pathfinding/astar.js.map +1 -0
  156. package/dist/src/pathfinding/index.d.ts +3 -0
  157. package/dist/src/pathfinding/index.d.ts.map +1 -0
  158. package/dist/src/pathfinding/index.js +3 -0
  159. package/dist/src/pathfinding/index.js.map +1 -0
  160. package/dist/src/pathfinding/utils.d.ts +18 -0
  161. package/dist/src/pathfinding/utils.d.ts.map +1 -0
  162. package/dist/src/pathfinding/utils.js +68 -0
  163. package/dist/src/pathfinding/utils.js.map +1 -0
  164. package/dist/src/types/index.d.ts +9 -0
  165. package/dist/src/types/index.d.ts.map +1 -1
  166. package/dist/src/utils/graphNode.d.ts +8 -0
  167. package/dist/src/utils/graphNode.d.ts.map +1 -0
  168. package/dist/src/utils/graphNode.js +2 -0
  169. package/dist/src/utils/graphNode.js.map +1 -0
  170. package/dist/src/utils/priorityQueue.d.ts +21 -0
  171. package/dist/src/utils/priorityQueue.d.ts.map +1 -0
  172. package/dist/src/utils/priorityQueue.js +101 -0
  173. package/dist/src/utils/priorityQueue.js.map +1 -0
  174. package/package.json +3 -1
  175. package/src/algorithms/centrality/betweenness.ts +25 -7
  176. package/src/algorithms/centrality/closeness.ts +7 -7
  177. package/src/algorithms/centrality/degree.ts +1 -1
  178. package/src/algorithms/centrality/eigenvector.ts +162 -0
  179. package/src/algorithms/centrality/hits.ts +210 -0
  180. package/src/algorithms/centrality/index.ts +6 -0
  181. package/src/algorithms/centrality/katz.ts +139 -0
  182. package/src/algorithms/centrality/pagerank.ts +3 -3
  183. package/src/algorithms/community/girvan-newman.ts +372 -0
  184. package/src/algorithms/community/index.ts +13 -0
  185. package/src/algorithms/community/label-propagation.ts +392 -0
  186. package/src/algorithms/community/leiden.ts +572 -0
  187. package/src/algorithms/community/louvain.ts +298 -0
  188. package/src/algorithms/components/connected.ts +7 -7
  189. package/src/algorithms/index.ts +21 -0
  190. package/src/algorithms/matching/bipartite.ts +179 -0
  191. package/src/algorithms/matching/index.ts +8 -0
  192. package/src/algorithms/matching/isomorphism.ts +392 -0
  193. package/src/algorithms/mst/index.ts +4 -0
  194. package/src/algorithms/mst/kruskal.ts +61 -0
  195. package/src/algorithms/mst/prim.ts +80 -0
  196. package/src/algorithms/shortest-path/bellman-ford.ts +2 -2
  197. package/src/algorithms/shortest-path/dijkstra.ts +5 -5
  198. package/src/algorithms/shortest-path/floyd-warshall.ts +170 -0
  199. package/src/algorithms/shortest-path/index.ts +2 -0
  200. package/src/algorithms/traversal/bfs.ts +2 -2
  201. package/src/algorithms/traversal/dfs.ts +11 -11
  202. package/src/clustering/hierarchical.ts +555 -0
  203. package/src/clustering/index.ts +6 -0
  204. package/src/clustering/k-core.ts +460 -0
  205. package/src/clustering/mcl.ts +454 -0
  206. package/src/clustering/spectral.ts +545 -0
  207. package/src/core/graph.ts +34 -6
  208. package/src/data-structures/union-find.ts +2 -2
  209. package/src/flow/ford-fulkerson.ts +459 -0
  210. package/src/flow/index.ts +2 -0
  211. package/src/flow/min-cut.ts +482 -0
  212. package/src/index.ts +2 -0
  213. package/src/link-prediction/adamic-adar.ts +353 -0
  214. package/src/link-prediction/common-neighbors.ts +252 -0
  215. package/src/link-prediction/index.ts +20 -0
  216. package/src/pathfinding/astar.ts +229 -0
  217. package/src/pathfinding/index.ts +2 -0
  218. package/src/pathfinding/utils.ts +87 -0
  219. package/src/types/index.ts +11 -0
  220. package/src/utils/graphNode.ts +7 -0
  221. package/src/utils/priorityQueue.ts +120 -0
  222. package/dist/test/browser/basic.test.d.ts +0 -2
  223. package/dist/test/browser/basic.test.d.ts.map +0 -1
  224. package/dist/test/browser/basic.test.js +0 -70
  225. package/dist/test/browser/basic.test.js.map +0 -1
  226. package/dist/test/unit/bellman-ford.test.d.ts +0 -2
  227. package/dist/test/unit/bellman-ford.test.d.ts.map +0 -1
  228. package/dist/test/unit/bellman-ford.test.js +0 -231
  229. package/dist/test/unit/bellman-ford.test.js.map +0 -1
  230. package/dist/test/unit/betweenness-centrality.test.d.ts +0 -2
  231. package/dist/test/unit/betweenness-centrality.test.d.ts.map +0 -1
  232. package/dist/test/unit/betweenness-centrality.test.js +0 -311
  233. package/dist/test/unit/betweenness-centrality.test.js.map +0 -1
  234. package/dist/test/unit/bfs.test.d.ts +0 -2
  235. package/dist/test/unit/bfs.test.d.ts.map +0 -1
  236. package/dist/test/unit/bfs.test.js +0 -277
  237. package/dist/test/unit/bfs.test.js.map +0 -1
  238. package/dist/test/unit/closeness.test.d.ts +0 -2
  239. package/dist/test/unit/closeness.test.d.ts.map +0 -1
  240. package/dist/test/unit/closeness.test.js +0 -266
  241. package/dist/test/unit/closeness.test.js.map +0 -1
  242. package/dist/test/unit/connected-components.test.d.ts +0 -2
  243. package/dist/test/unit/connected-components.test.d.ts.map +0 -1
  244. package/dist/test/unit/connected-components.test.js +0 -355
  245. package/dist/test/unit/connected-components.test.js.map +0 -1
  246. package/dist/test/unit/degree-centrality.test.d.ts +0 -2
  247. package/dist/test/unit/degree-centrality.test.d.ts.map +0 -1
  248. package/dist/test/unit/degree-centrality.test.js +0 -260
  249. package/dist/test/unit/degree-centrality.test.js.map +0 -1
  250. package/dist/test/unit/dfs.test.d.ts +0 -2
  251. package/dist/test/unit/dfs.test.d.ts.map +0 -1
  252. package/dist/test/unit/dfs.test.js +0 -350
  253. package/dist/test/unit/dfs.test.js.map +0 -1
  254. package/dist/test/unit/dijkstra.test.d.ts +0 -2
  255. package/dist/test/unit/dijkstra.test.d.ts.map +0 -1
  256. package/dist/test/unit/dijkstra.test.js +0 -311
  257. package/dist/test/unit/dijkstra.test.js.map +0 -1
  258. package/dist/test/unit/graph.test.d.ts +0 -2
  259. package/dist/test/unit/graph.test.d.ts.map +0 -1
  260. package/dist/test/unit/graph.test.js +0 -175
  261. package/dist/test/unit/graph.test.js.map +0 -1
  262. package/dist/test/unit/pagerank.test.d.ts +0 -2
  263. package/dist/test/unit/pagerank.test.d.ts.map +0 -1
  264. package/dist/test/unit/pagerank.test.js +0 -281
  265. package/dist/test/unit/pagerank.test.js.map +0 -1
  266. package/dist/test/unit/priority-queue.test.d.ts +0 -2
  267. package/dist/test/unit/priority-queue.test.d.ts.map +0 -1
  268. package/dist/test/unit/priority-queue.test.js +0 -189
  269. package/dist/test/unit/priority-queue.test.js.map +0 -1
  270. package/dist/test/unit/union-find.test.d.ts +0 -2
  271. package/dist/test/unit/union-find.test.d.ts.map +0 -1
  272. package/dist/test/unit/union-find.test.js +0 -245
  273. package/dist/test/unit/union-find.test.js.map +0 -1
@@ -0,0 +1,454 @@
1
+ import type {Graph} from "../core/graph.js";
2
+ import type {NodeId} from "../types/index.js";
3
+
4
+ /**
5
+ * Markov Clustering (MCL) algorithm implementation
6
+ *
7
+ * MCL simulates flow in graphs and finds clusters based on the notion that
8
+ * random walks stay within clusters and rarely move between clusters.
9
+ * The algorithm alternates between expansion (matrix squaring) and
10
+ * inflation (element-wise powering and normalization).
11
+ *
12
+ * Time complexity: O(V³) per iteration
13
+ * Space complexity: O(V²)
14
+ */
15
+
16
+ export interface MCLOptions {
17
+ expansion?: number; // Expansion parameter (default: 2)
18
+ inflation?: number; // Inflation parameter (default: 2)
19
+ maxIterations?: number; // Maximum iterations (default: 100)
20
+ tolerance?: number; // Convergence tolerance (default: 1e-6)
21
+ pruningThreshold?: number; // Pruning threshold (default: 1e-5)
22
+ selfLoops?: boolean; // Add self-loops (default: true)
23
+ }
24
+
25
+ export interface MCLResult {
26
+ communities: NodeId[][];
27
+ attractors: Set<NodeId>; // Attractor nodes (cluster representatives)
28
+ iterations: number;
29
+ converged: boolean;
30
+ }
31
+
32
+ /**
33
+ * Perform Markov Clustering on a graph
34
+ */
35
+ export function markovClustering(
36
+ graph: Graph,
37
+ options: MCLOptions = {},
38
+ ): MCLResult {
39
+ const {
40
+ expansion = 2,
41
+ inflation = 2,
42
+ maxIterations = 100,
43
+ tolerance = 1e-6,
44
+ pruningThreshold = 1e-5,
45
+ selfLoops = true,
46
+ } = options;
47
+
48
+ const nodes = Array.from(graph.nodes());
49
+ const nodeIds = nodes.map((node) => node.id);
50
+ const n = nodeIds.length;
51
+
52
+ if (n === 0) {
53
+ return {
54
+ communities: [],
55
+ attractors: new Set(),
56
+ iterations: 0,
57
+ converged: true,
58
+ };
59
+ }
60
+
61
+ // Build initial transition matrix
62
+ let matrix = buildTransitionMatrix(graph, nodeIds, selfLoops);
63
+
64
+ let converged = false;
65
+ let iteration = 0;
66
+
67
+ for (iteration = 0; iteration < maxIterations; iteration++) {
68
+ const oldMatrix = matrix.map((row) => [... row]);
69
+
70
+ // Expansion step (matrix multiplication)
71
+ matrix = matrixPower(matrix, expansion);
72
+
73
+ // Inflation step (element-wise powering and column normalization)
74
+ matrix = inflate(matrix, inflation);
75
+
76
+ // Pruning step (remove small values)
77
+ matrix = prune(matrix, pruningThreshold);
78
+
79
+ // Check for convergence
80
+ if (hasConverged(oldMatrix, matrix, tolerance)) {
81
+ converged = true;
82
+ break;
83
+ }
84
+ }
85
+
86
+ // Extract clusters from final matrix
87
+ const {communities, attractors} = extractClusters(matrix, nodeIds);
88
+
89
+ return {
90
+ communities,
91
+ attractors,
92
+ iterations: iteration + 1,
93
+ converged,
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Build initial transition matrix from graph
99
+ */
100
+ function buildTransitionMatrix(graph: Graph, nodeIds: NodeId[], selfLoops: boolean): number[][] {
101
+ const n = nodeIds.length;
102
+ const matrix = Array.from({length: n}, (): number[] => Array(n).fill(0) as number[]);
103
+ const nodeToIndex = new Map<NodeId, number>();
104
+
105
+ nodeIds.forEach((id, index) => nodeToIndex.set(id, index));
106
+
107
+ // Fill adjacency values
108
+ for (let i = 0; i < n; i++) {
109
+ const nodeId = nodeIds[i];
110
+ if (!nodeId) {
111
+ continue;
112
+ }
113
+
114
+ const neighbors = graph.neighbors(nodeId);
115
+
116
+ for (const neighbor of neighbors) {
117
+ const j = nodeToIndex.get(neighbor);
118
+ if (j !== undefined) {
119
+ const edge = graph.getEdge(nodeId, neighbor);
120
+ const weight = edge?.weight ?? 1;
121
+ const row = matrix[i];
122
+ if (!row) {
123
+ continue;
124
+ }
125
+
126
+ row[j] = weight;
127
+ }
128
+ }
129
+
130
+ // Add self-loops
131
+ if (selfLoops) {
132
+ const row = matrix[i];
133
+ if (!row) {
134
+ continue;
135
+ }
136
+
137
+ row[i] = 1;
138
+ }
139
+ }
140
+
141
+ // Column-normalize the matrix
142
+ for (let j = 0; j < n; j++) {
143
+ let colSum = 0;
144
+ for (let i = 0; i < n; i++) {
145
+ const val = matrix[i]?.[j];
146
+ if (val !== undefined) {
147
+ colSum += val;
148
+ }
149
+ }
150
+
151
+ if (colSum > 0) {
152
+ for (let i = 0; i < n; i++) {
153
+ const row = matrix[i];
154
+ if (!row) {
155
+ continue;
156
+ }
157
+
158
+ const val = row[j];
159
+ if (val !== undefined) {
160
+ row[j] = val / colSum;
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ return matrix;
167
+ }
168
+
169
+ /**
170
+ * Raise matrix to a power (for expansion step)
171
+ */
172
+ function matrixPower(matrix: number[][], power: number): number[][] {
173
+ if (power === 1) {
174
+ return matrix;
175
+ }
176
+
177
+ if (power === 2) {
178
+ return matrixMultiply(matrix, matrix);
179
+ }
180
+
181
+ let result = matrix;
182
+ for (let i = 1; i < power; i++) {
183
+ result = matrixMultiply(result, matrix);
184
+ }
185
+ return result;
186
+ }
187
+
188
+ /**
189
+ * Multiply two matrices
190
+ */
191
+ function matrixMultiply(a: number[][], b: number[][]): number[][] {
192
+ const n = a.length;
193
+ const m = b[0]?.length ?? 0;
194
+ const p = b.length;
195
+
196
+ const result: number[][] = Array.from({length: n}, () => Array(m).fill(0) as number[]);
197
+
198
+ for (let i = 0; i < n; i++) {
199
+ for (let j = 0; j < m; j++) {
200
+ for (let k = 0; k < p; k++) {
201
+ const aVal = a[i]?.[k] ?? 0;
202
+ const bVal = b[k]?.[j] ?? 0;
203
+ const resultRow = result[i];
204
+ if (!resultRow) {
205
+ continue;
206
+ }
207
+
208
+ const prevVal = resultRow[j];
209
+ if (prevVal !== undefined) {
210
+ resultRow[j] = prevVal + (aVal * bVal);
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ return result;
217
+ }
218
+
219
+ /**
220
+ * Inflation step: element-wise powering and column normalization
221
+ */
222
+ function inflate(matrix: number[][], inflation: number): number[][] {
223
+ const n = matrix.length;
224
+ const result: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
225
+
226
+ // Element-wise powering
227
+ for (let i = 0; i < n; i++) {
228
+ for (let j = 0; j < n; j++) {
229
+ const val = matrix[i]?.[j];
230
+ if (val !== undefined) {
231
+ const resultRow = result[i];
232
+ if (resultRow) {
233
+ resultRow[j] = Math.pow(val, inflation);
234
+ }
235
+ }
236
+ }
237
+ }
238
+
239
+ // Column normalization
240
+ for (let j = 0; j < n; j++) {
241
+ let colSum = 0;
242
+ for (let i = 0; i < n; i++) {
243
+ const val = result[i]?.[j];
244
+ if (val !== undefined) {
245
+ colSum += val;
246
+ }
247
+ }
248
+
249
+ if (colSum > 0) {
250
+ for (let i = 0; i < n; i++) {
251
+ const row = result[i];
252
+ if (!row) {
253
+ continue;
254
+ }
255
+
256
+ const val = row[j];
257
+ if (val !== undefined) {
258
+ row[j] = val / colSum;
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ return result;
265
+ }
266
+
267
+ /**
268
+ * Pruning step: remove small values
269
+ */
270
+ function prune(matrix: number[][], threshold: number): number[][] {
271
+ const n = matrix.length;
272
+ const result: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
273
+
274
+ for (let i = 0; i < n; i++) {
275
+ for (let j = 0; j < n; j++) {
276
+ const matrixRow = matrix[i];
277
+ if (!matrixRow) {
278
+ continue;
279
+ }
280
+
281
+ const matrixVal = matrixRow[j];
282
+ if (matrixVal !== undefined && matrixVal >= threshold) {
283
+ const resultRow = result[i];
284
+ if (resultRow) {
285
+ resultRow[j] = matrixVal;
286
+ }
287
+ }
288
+ }
289
+ }
290
+
291
+ // Re-normalize columns after pruning
292
+ for (let j = 0; j < n; j++) {
293
+ let colSum = 0;
294
+ for (let i = 0; i < n; i++) {
295
+ const resultRow = result[i];
296
+ if (!resultRow) {
297
+ continue;
298
+ }
299
+
300
+ const val = resultRow[j];
301
+ if (val !== undefined) {
302
+ colSum += val;
303
+ }
304
+ }
305
+
306
+ if (colSum > 0) {
307
+ for (let i = 0; i < n; i++) {
308
+ const resultRow = result[i];
309
+ if (!resultRow) {
310
+ continue;
311
+ }
312
+
313
+ const val = resultRow[j];
314
+ if (val !== undefined) {
315
+ resultRow[j] = val / colSum;
316
+ }
317
+ }
318
+ }
319
+ }
320
+
321
+ return result;
322
+ }
323
+
324
+ /**
325
+ * Check if the algorithm has converged
326
+ */
327
+ function hasConverged(oldMatrix: number[][], newMatrix: number[][], tolerance: number): boolean {
328
+ const n = oldMatrix.length;
329
+
330
+ for (let i = 0; i < n; i++) {
331
+ for (let j = 0; j < n; j++) {
332
+ const oldRow = oldMatrix[i];
333
+ const newRow = newMatrix[i];
334
+ if (!oldRow || !newRow) {
335
+ continue;
336
+ }
337
+
338
+ const oldVal = oldRow[j];
339
+ const newVal = newRow[j];
340
+ if (oldVal !== undefined && newVal !== undefined && Math.abs(oldVal - newVal) > tolerance) {
341
+ return false;
342
+ }
343
+ }
344
+ }
345
+
346
+ return true;
347
+ }
348
+
349
+ /**
350
+ * Extract clusters from the final matrix
351
+ */
352
+ function extractClusters(matrix: number[][], nodeIds: NodeId[]): {
353
+ communities: NodeId[][];
354
+ attractors: Set<NodeId>;
355
+ } {
356
+ const n = matrix.length;
357
+ const attractors = new Set<NodeId>();
358
+ const communities: NodeId[][] = [];
359
+ const nodeToCluster = new Map<number, number>();
360
+
361
+ // Find attractors (columns with non-zero diagonal elements)
362
+ for (let i = 0; i < n; i++) {
363
+ const matrixRow = matrix[i];
364
+ if (!matrixRow) {
365
+ continue;
366
+ }
367
+
368
+ const diagonalVal = matrixRow[i];
369
+ const nodeId = nodeIds[i];
370
+ if (diagonalVal !== undefined && diagonalVal > 0 && nodeId !== undefined) {
371
+ attractors.add(nodeId);
372
+ }
373
+ }
374
+
375
+ // Assign nodes to clusters based on columns
376
+ let clusterIndex = 0;
377
+ for (let j = 0; j < n; j++) {
378
+ // Find nodes that belong to this cluster (column)
379
+ const clusterNodes: number[] = [];
380
+ for (let i = 0; i < n; i++) {
381
+ const matrixRow = matrix[i];
382
+ if (!matrixRow) {
383
+ continue;
384
+ }
385
+
386
+ const val = matrixRow[j];
387
+ if (val !== undefined && val > 0 && !nodeToCluster.has(i)) {
388
+ clusterNodes.push(i);
389
+ nodeToCluster.set(i, clusterIndex);
390
+ }
391
+ }
392
+
393
+ if (clusterNodes.length > 0) {
394
+ communities.push(clusterNodes.map((idx) => {
395
+ const nodeId = nodeIds[idx];
396
+ return nodeId;
397
+ }).filter((node): node is NodeId => node !== undefined));
398
+ clusterIndex++;
399
+ }
400
+ }
401
+
402
+ // Handle isolated nodes
403
+ for (let i = 0; i < n; i++) {
404
+ if (!nodeToCluster.has(i)) {
405
+ const nodeId = nodeIds[i];
406
+ if (nodeId !== undefined) {
407
+ communities.push([nodeId]);
408
+ }
409
+
410
+ nodeToCluster.set(i, clusterIndex++);
411
+ }
412
+ }
413
+
414
+ return {communities, attractors};
415
+ }
416
+
417
+ /**
418
+ * Calculate modularity of MCL clustering result
419
+ */
420
+ export function calculateMCLModularity(
421
+ graph: Graph,
422
+ communities: NodeId[][],
423
+ ): number {
424
+ const m = graph.totalEdgeCount;
425
+ if (m === 0) {
426
+ return 0;
427
+ }
428
+
429
+ let modularity = 0;
430
+ const communityMap = new Map<NodeId, number>();
431
+
432
+ // Build community map
433
+ communities.forEach((community, index) => {
434
+ community.forEach((nodeId) => {
435
+ communityMap.set(nodeId, index);
436
+ });
437
+ });
438
+
439
+ // Calculate modularity
440
+ for (const edge of graph.edges()) {
441
+ const sourceCommunity = communityMap.get(edge.source);
442
+ const targetCommunity = communityMap.get(edge.target);
443
+
444
+ if (sourceCommunity !== undefined && targetCommunity !== undefined && sourceCommunity === targetCommunity) {
445
+ modularity += 1;
446
+ }
447
+
448
+ const sourceDegree = graph.degree(edge.source);
449
+ const targetDegree = graph.degree(edge.target);
450
+ modularity -= (sourceDegree * targetDegree) / (2 * m);
451
+ }
452
+
453
+ return modularity / (2 * m);
454
+ }