@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,298 @@
1
+ import {Graph} from "../../core/graph.js";
2
+ import type {CommunityResult, LouvainOptions, NodeId} from "../../types/index.js";
3
+
4
+ /**
5
+ * Louvain community detection algorithm
6
+ *
7
+ * Implements the Louvain method for community detection in graphs.
8
+ * Uses modularity optimization to find community structure.
9
+ *
10
+ * References:
11
+ * - Blondel, V. D., Guillaume, J. L., Lambiotte, R., & Lefebvre, E. (2008).
12
+ * Fast unfolding of communities in large networks.
13
+ * Journal of statistical mechanics: theory and experiment, 2008(10), P10008.
14
+ *
15
+ * @param graph - The input graph
16
+ * @param options - Algorithm options
17
+ * @returns Community detection result with communities, modularity, and iterations
18
+ */
19
+ export function louvain(
20
+ graph: Graph,
21
+ options: LouvainOptions = {},
22
+ ): CommunityResult {
23
+ const resolution = options.resolution ?? 1.0;
24
+ const maxIterations = options.maxIterations ?? 100;
25
+ const tolerance = options.tolerance ?? 1e-6;
26
+
27
+ // Initialize: each node in its own community
28
+ const communities = initializeCommunities(graph);
29
+ let modularity = calculateModularity(graph, communities, resolution);
30
+ let iteration = 0;
31
+ let improved = true;
32
+
33
+ while (iteration < maxIterations && improved) {
34
+ // Phase 1: Local optimization
35
+ improved = louvainPhase1(graph, communities, resolution);
36
+
37
+ if (improved) {
38
+ const newModularity = calculateModularity(graph, communities, resolution);
39
+
40
+ // Check for convergence
41
+ if ((newModularity - modularity) < tolerance) {
42
+ break;
43
+ }
44
+
45
+ modularity = newModularity;
46
+ iteration++;
47
+ }
48
+ }
49
+
50
+ return {
51
+ communities: extractCommunities(communities),
52
+ modularity: calculateModularity(graph, communities, resolution),
53
+ iterations: iteration,
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Initialize communities: each node in its own community
59
+ */
60
+ function initializeCommunities(graph: Graph): Map<NodeId, number> {
61
+ const communities = new Map<NodeId, number>();
62
+ let communityId = 0;
63
+
64
+ for (const node of graph.nodes()) {
65
+ communities.set(node.id, communityId++);
66
+ }
67
+
68
+ return communities;
69
+ }
70
+
71
+ /**
72
+ * Phase 1 of Louvain algorithm: Local optimization
73
+ * For each node, try moving to neighboring communities and keep the move
74
+ * that provides the best modularity gain.
75
+ */
76
+ function louvainPhase1(
77
+ graph: Graph,
78
+ communities: Map<NodeId, number>,
79
+ resolution: number,
80
+ ): boolean {
81
+ let globalImprovement = false;
82
+ let localImprovement = true;
83
+
84
+ while (localImprovement) {
85
+ localImprovement = false;
86
+
87
+ for (const node of graph.nodes()) {
88
+ const nodeId = node.id;
89
+ const currentCommunity = communities.get(nodeId);
90
+
91
+ if (currentCommunity === undefined) {
92
+ continue;
93
+ }
94
+
95
+ // Calculate current modularity contribution
96
+ const currentModularity = nodeModularityContribution(
97
+ graph, nodeId, currentCommunity, communities, resolution,
98
+ );
99
+
100
+ let bestCommunity = currentCommunity;
101
+ let bestModularity = currentModularity;
102
+
103
+ // Try moving to neighboring communities
104
+ const neighborCommunities = getNeighborCommunities(graph, nodeId, communities);
105
+
106
+ for (const neighborCommunity of neighborCommunities) {
107
+ if (neighborCommunity === currentCommunity) {
108
+ continue;
109
+ }
110
+
111
+ const newModularity = nodeModularityContribution(
112
+ graph, nodeId, neighborCommunity, communities, resolution,
113
+ );
114
+
115
+ if (newModularity > bestModularity) {
116
+ bestModularity = newModularity;
117
+ bestCommunity = neighborCommunity;
118
+ }
119
+ }
120
+
121
+ // Move node if improvement found
122
+ if (bestCommunity !== currentCommunity) {
123
+ communities.set(nodeId, bestCommunity);
124
+ localImprovement = true;
125
+ globalImprovement = true;
126
+ }
127
+ }
128
+ }
129
+
130
+ return globalImprovement;
131
+ }
132
+
133
+ /**
134
+ * Calculate modularity contribution of a node to a specific community
135
+ */
136
+ function nodeModularityContribution(
137
+ graph: Graph,
138
+ nodeId: NodeId,
139
+ community: number,
140
+ communities: Map<NodeId, number>,
141
+ resolution: number,
142
+ ): number {
143
+ const totalEdgeWeight = getTotalEdgeWeight(graph);
144
+ if (totalEdgeWeight === 0) {
145
+ return 0;
146
+ }
147
+
148
+ let internalLinks = 0;
149
+ let nodeDegree = 0;
150
+ let communityDegree = 0;
151
+
152
+ // Calculate node's connections to the community
153
+ for (const neighbor of graph.neighbors(nodeId)) {
154
+ const edge = graph.getEdge(nodeId, neighbor);
155
+ const weight = edge?.weight ?? 1;
156
+ nodeDegree += weight;
157
+
158
+ if (communities.get(neighbor) === community) {
159
+ internalLinks += weight;
160
+ }
161
+ }
162
+
163
+ // Calculate total degree of the community (excluding the node if it's currently in it)
164
+ for (const [otherNodeId, otherCommunity] of communities) {
165
+ if (otherCommunity === community && otherNodeId !== nodeId) {
166
+ communityDegree += getNodeDegree(graph, otherNodeId);
167
+ }
168
+ }
169
+
170
+ // Modularity formula: Q = (1/2m) * Σ[A_ij - (k_i * k_j)/(2m)] * δ(c_i, c_j)
171
+ const modularityIncrease = (internalLinks - ((resolution * nodeDegree * communityDegree) / (2 * totalEdgeWeight))) / totalEdgeWeight;
172
+
173
+ return modularityIncrease;
174
+ }
175
+
176
+ /**
177
+ * Get communities of neighboring nodes
178
+ */
179
+ function getNeighborCommunities(
180
+ graph: Graph,
181
+ nodeId: NodeId,
182
+ communities: Map<NodeId, number>,
183
+ ): Set<number> {
184
+ const neighborCommunities = new Set<number>();
185
+
186
+ for (const neighbor of graph.neighbors(nodeId)) {
187
+ const community = communities.get(neighbor);
188
+ if (community !== undefined) {
189
+ neighborCommunities.add(community);
190
+ }
191
+ }
192
+
193
+ return neighborCommunities;
194
+ }
195
+
196
+ /**
197
+ * Calculate total modularity of the current community assignment
198
+ */
199
+ function calculateModularity(
200
+ graph: Graph,
201
+ communities: Map<NodeId, number>,
202
+ resolution: number,
203
+ ): number {
204
+ const totalEdgeWeight = getTotalEdgeWeight(graph);
205
+ if (totalEdgeWeight === 0) {
206
+ return 0;
207
+ }
208
+
209
+ let modularity = 0;
210
+
211
+ // For undirected graphs, we need to be careful not to double-count edges
212
+ const countedEdges = new Set<string>();
213
+
214
+ // Calculate modularity: Q = (1/2m) * Σ[A_ij - γ(k_i * k_j)/(2m)] * δ(c_i, c_j)
215
+ for (const nodeI of graph.nodes()) {
216
+ for (const nodeJ of graph.nodes()) {
217
+ // Skip if already counted this pair in undirected graph
218
+ if (!graph.isDirected) {
219
+ const nodeIStr = String(nodeI.id);
220
+ const nodeJStr = String(nodeJ.id);
221
+ const edgeKey = nodeIStr <= nodeJStr ? `${nodeIStr}-${nodeJStr}` : `${nodeJStr}-${nodeIStr}`;
222
+ if (countedEdges.has(edgeKey)) {
223
+ continue;
224
+ }
225
+
226
+ countedEdges.add(edgeKey);
227
+ }
228
+
229
+ if (communities.get(nodeI.id) === communities.get(nodeJ.id)) {
230
+ const edge = graph.getEdge(nodeI.id, nodeJ.id);
231
+ const reverseEdge = !graph.isDirected ? graph.getEdge(nodeJ.id, nodeI.id) : null;
232
+
233
+ let edgeWeight = 0;
234
+ if (edge) {
235
+ edgeWeight += edge.weight ?? 1;
236
+ }
237
+
238
+ if (reverseEdge && nodeI.id !== nodeJ.id) {
239
+ edgeWeight += reverseEdge.weight ?? 1;
240
+ }
241
+
242
+ const degreeI = getNodeDegree(graph, nodeI.id);
243
+ const degreeJ = getNodeDegree(graph, nodeJ.id);
244
+
245
+ modularity += edgeWeight - ((resolution * degreeI * degreeJ) / (2 * totalEdgeWeight));
246
+ }
247
+ }
248
+ }
249
+
250
+ return modularity / (2 * totalEdgeWeight);
251
+ }
252
+
253
+ /**
254
+ * Extract final community structure
255
+ */
256
+ function extractCommunities(communities: Map<NodeId, number>): NodeId[][] {
257
+ const communityMap = new Map<number, NodeId[]>();
258
+
259
+ for (const [nodeId, community] of communities) {
260
+ if (!communityMap.has(community)) {
261
+ communityMap.set(community, []);
262
+ }
263
+
264
+ const communityNodes = communityMap.get(community);
265
+ if (communityNodes) {
266
+ communityNodes.push(nodeId);
267
+ }
268
+ }
269
+
270
+ return Array.from(communityMap.values());
271
+ }
272
+
273
+ /**
274
+ * Calculate total edge weight in the graph
275
+ */
276
+ function getTotalEdgeWeight(graph: Graph): number {
277
+ let totalWeight = 0;
278
+
279
+ for (const edge of graph.edges()) {
280
+ totalWeight += edge.weight ?? 1;
281
+ }
282
+
283
+ return totalWeight;
284
+ }
285
+
286
+ /**
287
+ * Get the total degree (sum of edge weights) for a node
288
+ */
289
+ function getNodeDegree(graph: Graph, nodeId: NodeId): number {
290
+ let degree = 0;
291
+
292
+ for (const neighbor of graph.neighbors(nodeId)) {
293
+ const edge = graph.getEdge(nodeId, neighbor);
294
+ degree += edge?.weight ?? 1;
295
+ }
296
+
297
+ return degree;
298
+ }
@@ -26,7 +26,7 @@ export function connectedComponents(graph: Graph): NodeId[][] {
26
26
  const unionFind = new UnionFind(nodes);
27
27
 
28
28
  // Union connected nodes
29
- for (const edge of graph.edges()) {
29
+ for (const edge of Array.from(graph.edges())) {
30
30
  unionFind.union(edge.source, edge.target);
31
31
  }
32
32
 
@@ -44,7 +44,7 @@ export function connectedComponentsDFS(graph: Graph): NodeId[][] {
44
44
  const visited = new Set<NodeId>();
45
45
  const components: NodeId[][] = [];
46
46
 
47
- for (const node of graph.nodes()) {
47
+ for (const node of Array.from(graph.nodes())) {
48
48
  if (!visited.has(node.id)) {
49
49
  const component: NodeId[] = [];
50
50
  dfsComponent(graph, node.id, visited, component);
@@ -67,7 +67,7 @@ function dfsComponent(
67
67
  visited.add(nodeId);
68
68
  component.push(nodeId);
69
69
 
70
- for (const neighbor of graph.neighbors(nodeId)) {
70
+ for (const neighbor of Array.from(graph.neighbors(nodeId))) {
71
71
  if (!visited.has(neighbor)) {
72
72
  dfsComponent(graph, neighbor, visited, component);
73
73
  }
@@ -148,7 +148,7 @@ export function stronglyConnectedComponents(graph: Graph): NodeId[][] {
148
148
  onStack.add(nodeId);
149
149
 
150
150
  // Consider successors
151
- for (const neighbor of graph.neighbors(nodeId)) {
151
+ for (const neighbor of Array.from(graph.neighbors(nodeId))) {
152
152
  if (!indices.has(neighbor)) {
153
153
  // Successor has not yet been visited; recurse on it
154
154
  tarjanSCC(neighbor);
@@ -226,7 +226,7 @@ export function weaklyConnectedComponents(graph: Graph): NodeId[][] {
226
226
  const unionFind = new UnionFind(nodes);
227
227
 
228
228
  // Union nodes connected by edges (ignore direction)
229
- for (const edge of graph.edges()) {
229
+ for (const edge of Array.from(graph.edges())) {
230
230
  unionFind.union(edge.source, edge.target);
231
231
  }
232
232
 
@@ -275,13 +275,13 @@ export function condensationGraph(graph: Graph): {
275
275
  // Add edges between components
276
276
  const addedEdges = new Set<string>();
277
277
 
278
- for (const edge of graph.edges()) {
278
+ for (const edge of Array.from(graph.edges())) {
279
279
  const sourceComponent = componentMap.get(edge.source);
280
280
  const targetComponent = componentMap.get(edge.target);
281
281
 
282
282
  if (sourceComponent !== undefined && targetComponent !== undefined &&
283
283
  sourceComponent !== targetComponent) {
284
- const edgeKey = `${sourceComponent}-${targetComponent}`;
284
+ const edgeKey = `${String(sourceComponent)}-${String(targetComponent)}`;
285
285
 
286
286
  if (!addedEdges.has(edgeKey)) {
287
287
  condensedGraph.addEdge(sourceComponent, targetComponent);
@@ -13,3 +13,24 @@ export * from "./centrality/index.js";
13
13
 
14
14
  // Connected components algorithms
15
15
  export * from "./components/index.js";
16
+
17
+ // Minimum spanning tree algorithms
18
+ export * from "./mst/index.js";
19
+
20
+ // Community detection algorithms
21
+ export * from "./community/index.js";
22
+
23
+ // Pathfinding algorithms
24
+ export * from "../pathfinding/index.js";
25
+
26
+ // Flow algorithms
27
+ export * from "../flow/index.js";
28
+
29
+ // Clustering algorithms
30
+ export * from "../clustering/index.js";
31
+
32
+ // Matching algorithms
33
+ export * from "./matching/index.js";
34
+
35
+ // Link prediction algorithms
36
+ export * from "../link-prediction/index.js";
@@ -0,0 +1,179 @@
1
+ import type {Graph} from "../../core/graph.js";
2
+ import type {NodeId} from "../../types/index.js";
3
+
4
+ /**
5
+ * Maximum Bipartite Matching implementation using Hopcroft-Karp algorithm
6
+ *
7
+ * Finds the maximum matching in a bipartite graph. A matching is a set of edges
8
+ * without common vertices. Maximum matching has the largest possible number of edges.
9
+ *
10
+ * Time complexity: O(√V × E)
11
+ * Space complexity: O(V)
12
+ */
13
+
14
+ export interface BipartiteMatchingResult {
15
+ matching: Map<NodeId, NodeId>; // Maps left nodes to right nodes
16
+ size: number; // Number of matched pairs
17
+ }
18
+
19
+ export interface BipartiteMatchingOptions {
20
+ leftNodes?: Set<NodeId>; // Optional: specify left partition
21
+ rightNodes?: Set<NodeId>; // Optional: specify right partition
22
+ }
23
+
24
+ /**
25
+ * Find maximum matching in a bipartite graph using augmenting path algorithm
26
+ * (simplified implementation that's more reliable than Hopcroft-Karp for this context)
27
+ */
28
+ export function maximumBipartiteMatching(
29
+ graph: Graph,
30
+ options: BipartiteMatchingOptions = {},
31
+ ): BipartiteMatchingResult {
32
+ // If partitions not provided, try to infer them
33
+ let {leftNodes, rightNodes} = options;
34
+
35
+ if (!leftNodes || !rightNodes) {
36
+ const partition = bipartitePartition(graph);
37
+ if (!partition) {
38
+ throw new Error("Graph is not bipartite");
39
+ }
40
+
41
+ leftNodes = partition.left;
42
+ rightNodes = partition.right;
43
+ }
44
+
45
+ const matching = new Map<NodeId, NodeId>();
46
+ const matchRight = new Map<NodeId, NodeId>(); // Right to left matching
47
+
48
+ // Find augmenting paths using DFS
49
+ const visited = new Set<NodeId>();
50
+
51
+ const dfs = (u: NodeId): boolean => {
52
+ const neighbors = Array.from(graph.neighbors(u));
53
+
54
+ for (const v of neighbors) {
55
+ if (!rightNodes.has(v) || visited.has(v)) {
56
+ continue;
57
+ }
58
+
59
+ visited.add(v);
60
+
61
+ // If v is unmatched or we can find augmenting path from match of v
62
+ const matchedNode = matchRight.get(v);
63
+ if (!matchRight.has(v) || (matchedNode !== undefined && dfs(matchedNode))) {
64
+ matching.set(u, v);
65
+ matchRight.set(v, u);
66
+ return true;
67
+ }
68
+ }
69
+ return false;
70
+ };
71
+
72
+ // Try to find augmenting path for each left node
73
+ let matchingSize = 0;
74
+ for (const u of leftNodes) {
75
+ visited.clear();
76
+ if (dfs(u)) {
77
+ matchingSize++;
78
+ }
79
+ }
80
+
81
+ return {
82
+ matching,
83
+ size: matchingSize,
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Check if graph is bipartite and return the partition
89
+ */
90
+ export function bipartitePartition(graph: Graph): {left: Set<NodeId>, right: Set<NodeId>} | null {
91
+ const color = new Map<NodeId, boolean>();
92
+ const left = new Set<NodeId>();
93
+ const right = new Set<NodeId>();
94
+
95
+ const nodes = Array.from(graph.nodes());
96
+
97
+ for (const startNode of nodes) {
98
+ if (color.has(startNode.id)) {
99
+ continue;
100
+ }
101
+
102
+ // BFS to color the component
103
+ const queue = [startNode.id];
104
+ color.set(startNode.id, true);
105
+ left.add(startNode.id);
106
+
107
+ while (queue.length > 0) {
108
+ const u = queue.shift();
109
+ if (u === undefined) {
110
+ continue;
111
+ }
112
+
113
+ const uColor = color.get(u);
114
+ if (uColor === undefined) {
115
+ continue;
116
+ }
117
+
118
+ const neighbors = Array.from(graph.neighbors(u));
119
+ for (const v of neighbors) {
120
+ if (!color.has(v)) {
121
+ color.set(v, !uColor);
122
+ if (!uColor) {
123
+ left.add(v);
124
+ } else {
125
+ right.add(v);
126
+ }
127
+
128
+ queue.push(v);
129
+ } else if (color.get(v) === uColor) {
130
+ // Same color as neighbor - not bipartite
131
+ return null;
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ return {left, right};
138
+ }
139
+
140
+ /**
141
+ * Simple greedy bipartite matching algorithm
142
+ * Useful for comparison or when Hopcroft-Karp is overkill
143
+ */
144
+ export function greedyBipartiteMatching(
145
+ graph: Graph,
146
+ options: BipartiteMatchingOptions = {},
147
+ ): BipartiteMatchingResult {
148
+ let {leftNodes, rightNodes} = options;
149
+
150
+ if (!leftNodes || !rightNodes) {
151
+ const partition = bipartitePartition(graph);
152
+ if (!partition) {
153
+ throw new Error("Graph is not bipartite");
154
+ }
155
+
156
+ leftNodes = partition.left;
157
+ rightNodes = partition.right;
158
+ }
159
+
160
+ const matching = new Map<NodeId, NodeId>();
161
+ const matched = new Set<NodeId>();
162
+
163
+ for (const u of leftNodes) {
164
+ const neighbors = Array.from(graph.neighbors(u));
165
+
166
+ for (const v of neighbors) {
167
+ if (rightNodes.has(v) && !matched.has(v)) {
168
+ matching.set(u, v);
169
+ matched.add(v);
170
+ break;
171
+ }
172
+ }
173
+ }
174
+
175
+ return {
176
+ matching,
177
+ size: matching.size,
178
+ };
179
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Graph matching algorithms
3
+ */
4
+
5
+ export type {BipartiteMatchingOptions, BipartiteMatchingResult} from "./bipartite.js";
6
+ export {bipartitePartition, greedyBipartiteMatching, maximumBipartiteMatching} from "./bipartite.js";
7
+ export type {IsomorphismOptions, IsomorphismResult} from "./isomorphism.js";
8
+ export {findAllIsomorphisms, isGraphIsomorphic} from "./isomorphism.js";