@graphty/algorithms 1.1.0 → 1.3.1

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 (237) hide show
  1. package/README.md +820 -235
  2. package/dist/algorithms.d.ts +42 -2
  3. package/dist/algorithms.js +11099 -2
  4. package/dist/algorithms.js.map +1 -1
  5. package/dist/src/algorithms/centrality/betweenness.d.ts +4 -0
  6. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
  7. package/dist/src/algorithms/centrality/betweenness.js +124 -170
  8. package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
  9. package/dist/src/algorithms/centrality/closeness.d.ts +26 -1
  10. package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
  11. package/dist/src/algorithms/centrality/closeness.js +56 -149
  12. package/dist/src/algorithms/centrality/closeness.js.map +1 -1
  13. package/dist/src/algorithms/centrality/degree.js +3 -3
  14. package/dist/src/algorithms/centrality/degree.js.map +1 -1
  15. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +39 -0
  16. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -0
  17. package/dist/src/algorithms/centrality/delta-pagerank-simple.js +286 -0
  18. package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -0
  19. package/dist/src/algorithms/centrality/delta-pagerank.d.ts +80 -0
  20. package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -0
  21. package/dist/src/algorithms/centrality/delta-pagerank.js +294 -0
  22. package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -0
  23. package/dist/src/algorithms/centrality/index.d.ts +2 -0
  24. package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
  25. package/dist/src/algorithms/centrality/index.js +1 -0
  26. package/dist/src/algorithms/centrality/index.js.map +1 -1
  27. package/dist/src/algorithms/centrality/pagerank.d.ts +17 -0
  28. package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
  29. package/dist/src/algorithms/centrality/pagerank.js +45 -0
  30. package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
  31. package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
  32. package/dist/src/algorithms/community/girvan-newman.js +47 -54
  33. package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
  34. package/dist/src/algorithms/community/index.d.ts +2 -0
  35. package/dist/src/algorithms/community/index.d.ts.map +1 -1
  36. package/dist/src/algorithms/community/index.js.map +1 -1
  37. package/dist/src/algorithms/community/label-propagation.d.ts +13 -3
  38. package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
  39. package/dist/src/algorithms/community/label-propagation.js +50 -35
  40. package/dist/src/algorithms/community/label-propagation.js.map +1 -1
  41. package/dist/src/algorithms/community/leiden.d.ts +3 -2
  42. package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
  43. package/dist/src/algorithms/community/leiden.js +34 -39
  44. package/dist/src/algorithms/community/leiden.js.map +1 -1
  45. package/dist/src/algorithms/community/louvain-optimized.d.ts +112 -0
  46. package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -0
  47. package/dist/src/algorithms/community/louvain-optimized.js +303 -0
  48. package/dist/src/algorithms/community/louvain-optimized.js.map +1 -0
  49. package/dist/src/algorithms/community/louvain.d.ts.map +1 -1
  50. package/dist/src/algorithms/community/louvain.js +17 -76
  51. package/dist/src/algorithms/community/louvain.js.map +1 -1
  52. package/dist/src/algorithms/community/modularity-utils.d.ts +59 -0
  53. package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -0
  54. package/dist/src/algorithms/community/modularity-utils.js +112 -0
  55. package/dist/src/algorithms/community/modularity-utils.js.map +1 -0
  56. package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -1
  57. package/dist/src/algorithms/matching/bipartite.js +8 -40
  58. package/dist/src/algorithms/matching/bipartite.js.map +1 -1
  59. package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
  60. package/dist/src/algorithms/shortest-path/bellman-ford.js +1 -12
  61. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
  62. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +37 -0
  63. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -0
  64. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +193 -0
  65. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -0
  66. package/dist/src/algorithms/shortest-path/dijkstra.d.ts +5 -2
  67. package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
  68. package/dist/src/algorithms/shortest-path/dijkstra.js +15 -14
  69. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
  70. package/dist/src/algorithms/traversal/bfs-unified.d.ts +29 -0
  71. package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -0
  72. package/dist/src/algorithms/traversal/bfs-unified.js +333 -0
  73. package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -0
  74. package/dist/src/algorithms/traversal/bfs-variants.d.ts +54 -0
  75. package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -0
  76. package/dist/src/algorithms/traversal/bfs-variants.js +368 -0
  77. package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -0
  78. package/dist/src/algorithms/traversal/bfs.d.ts +4 -18
  79. package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
  80. package/dist/src/algorithms/traversal/bfs.js +5 -182
  81. package/dist/src/algorithms/traversal/bfs.js.map +1 -1
  82. package/dist/src/benchmark-all-algorithms.d.ts +23 -0
  83. package/dist/src/benchmark-all-algorithms.d.ts.map +1 -0
  84. package/dist/src/benchmark-all-algorithms.js +462 -0
  85. package/dist/src/benchmark-all-algorithms.js.map +1 -0
  86. package/dist/src/clustering/hierarchical.d.ts +16 -15
  87. package/dist/src/clustering/hierarchical.d.ts.map +1 -1
  88. package/dist/src/clustering/hierarchical.js +209 -223
  89. package/dist/src/clustering/hierarchical.js.map +1 -1
  90. package/dist/src/clustering/index.d.ts +4 -2
  91. package/dist/src/clustering/index.d.ts.map +1 -1
  92. package/dist/src/clustering/index.js +2 -2
  93. package/dist/src/clustering/index.js.map +1 -1
  94. package/dist/src/clustering/k-core.d.ts +16 -15
  95. package/dist/src/clustering/k-core.d.ts.map +1 -1
  96. package/dist/src/clustering/k-core.js +57 -21
  97. package/dist/src/clustering/k-core.js.map +1 -1
  98. package/dist/src/clustering/spectral.d.ts +8 -0
  99. package/dist/src/clustering/spectral.d.ts.map +1 -1
  100. package/dist/src/clustering/spectral.js +158 -35
  101. package/dist/src/clustering/spectral.js.map +1 -1
  102. package/dist/src/core/graph.js +4 -4
  103. package/dist/src/core/graph.js.map +1 -1
  104. package/dist/src/flow/ford-fulkerson.d.ts +17 -11
  105. package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
  106. package/dist/src/flow/ford-fulkerson.js +141 -217
  107. package/dist/src/flow/ford-fulkerson.js.map +1 -1
  108. package/dist/src/flow/min-cut.d.ts +6 -5
  109. package/dist/src/flow/min-cut.d.ts.map +1 -1
  110. package/dist/src/flow/min-cut.js +17 -8
  111. package/dist/src/flow/min-cut.js.map +1 -1
  112. package/dist/src/index.d.ts +2 -0
  113. package/dist/src/index.d.ts.map +1 -1
  114. package/dist/src/index.js +6 -0
  115. package/dist/src/index.js.map +1 -1
  116. package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -1
  117. package/dist/src/link-prediction/adamic-adar.js +17 -26
  118. package/dist/src/link-prediction/adamic-adar.js.map +1 -1
  119. package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
  120. package/dist/src/link-prediction/common-neighbors.js +7 -11
  121. package/dist/src/link-prediction/common-neighbors.js.map +1 -1
  122. package/dist/src/optimized/bit-packed.d.ts +143 -0
  123. package/dist/src/optimized/bit-packed.d.ts.map +1 -0
  124. package/dist/src/optimized/bit-packed.js +292 -0
  125. package/dist/src/optimized/bit-packed.js.map +1 -0
  126. package/dist/src/optimized/csr-graph.d.ts +86 -0
  127. package/dist/src/optimized/csr-graph.d.ts.map +1 -0
  128. package/dist/src/optimized/csr-graph.js +341 -0
  129. package/dist/src/optimized/csr-graph.js.map +1 -0
  130. package/dist/src/optimized/direction-optimized-bfs.d.ts +74 -0
  131. package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -0
  132. package/dist/src/optimized/direction-optimized-bfs.js +223 -0
  133. package/dist/src/optimized/direction-optimized-bfs.js.map +1 -0
  134. package/dist/src/optimized/graph-adapter.d.ts +62 -0
  135. package/dist/src/optimized/graph-adapter.d.ts.map +1 -0
  136. package/dist/src/optimized/graph-adapter.js +146 -0
  137. package/dist/src/optimized/graph-adapter.js.map +1 -0
  138. package/dist/src/optimized/index.d.ts +14 -0
  139. package/dist/src/optimized/index.d.ts.map +1 -0
  140. package/dist/src/optimized/index.js +28 -0
  141. package/dist/src/optimized/index.js.map +1 -0
  142. package/dist/src/research/grsbm.d.ts +83 -0
  143. package/dist/src/research/grsbm.d.ts.map +1 -0
  144. package/dist/src/research/grsbm.js +403 -0
  145. package/dist/src/research/grsbm.js.map +1 -0
  146. package/dist/src/research/index.d.ts +12 -0
  147. package/dist/src/research/index.d.ts.map +1 -0
  148. package/dist/src/research/index.js +14 -0
  149. package/dist/src/research/index.js.map +1 -0
  150. package/dist/src/research/sync.d.ts +49 -0
  151. package/dist/src/research/sync.d.ts.map +1 -0
  152. package/dist/src/research/sync.js +316 -0
  153. package/dist/src/research/sync.js.map +1 -0
  154. package/dist/src/research/terahac.d.ts +65 -0
  155. package/dist/src/research/terahac.d.ts.map +1 -0
  156. package/dist/src/research/terahac.js +379 -0
  157. package/dist/src/research/terahac.js.map +1 -0
  158. package/dist/src/types/index.d.ts +8 -0
  159. package/dist/src/types/index.d.ts.map +1 -1
  160. package/dist/src/utils/algorithm-utilities.d.ts +2 -0
  161. package/dist/src/utils/algorithm-utilities.d.ts.map +1 -0
  162. package/dist/src/utils/algorithm-utilities.js +2 -0
  163. package/dist/src/utils/algorithm-utilities.js.map +1 -0
  164. package/dist/src/utils/graph-converters.d.ts +79 -0
  165. package/dist/src/utils/graph-converters.d.ts.map +1 -0
  166. package/dist/src/utils/graph-converters.js +196 -0
  167. package/dist/src/utils/graph-converters.js.map +1 -0
  168. package/dist/src/utils/graph-utilities.d.ts +62 -0
  169. package/dist/src/utils/graph-utilities.d.ts.map +1 -0
  170. package/dist/src/utils/graph-utilities.js +148 -0
  171. package/dist/src/utils/graph-utilities.js.map +1 -0
  172. package/dist/src/utils/index.d.ts +5 -0
  173. package/dist/src/utils/index.d.ts.map +1 -0
  174. package/dist/src/utils/index.js +5 -0
  175. package/dist/src/utils/index.js.map +1 -0
  176. package/dist/src/utils/math-utilities.d.ts +52 -0
  177. package/dist/src/utils/math-utilities.d.ts.map +1 -0
  178. package/dist/src/utils/math-utilities.js +123 -0
  179. package/dist/src/utils/math-utilities.js.map +1 -0
  180. package/dist/src/utils/matrix-utilities.d.ts +2 -0
  181. package/dist/src/utils/matrix-utilities.d.ts.map +1 -0
  182. package/dist/src/utils/matrix-utilities.js +2 -0
  183. package/dist/src/utils/matrix-utilities.js.map +1 -0
  184. package/dist/src/utils/optimization-helpers.d.ts +33 -0
  185. package/dist/src/utils/optimization-helpers.d.ts.map +1 -0
  186. package/dist/src/utils/optimization-helpers.js +47 -0
  187. package/dist/src/utils/optimization-helpers.js.map +1 -0
  188. package/package.json +28 -4
  189. package/src/algorithms/centrality/betweenness.ts +167 -199
  190. package/src/algorithms/centrality/closeness.ts +73 -191
  191. package/src/algorithms/centrality/degree.ts +3 -3
  192. package/src/algorithms/centrality/delta-pagerank-simple.ts +366 -0
  193. package/src/algorithms/centrality/delta-pagerank.ts +435 -0
  194. package/src/algorithms/centrality/index.ts +2 -0
  195. package/src/algorithms/centrality/pagerank.ts +66 -0
  196. package/src/algorithms/community/girvan-newman.ts +52 -59
  197. package/src/algorithms/community/index.ts +2 -0
  198. package/src/algorithms/community/label-propagation.ts +64 -35
  199. package/src/algorithms/community/leiden.ts +40 -41
  200. package/src/algorithms/community/louvain-optimized.ts +445 -0
  201. package/src/algorithms/community/louvain.ts +24 -104
  202. package/src/algorithms/community/modularity-utils.ts +146 -0
  203. package/src/algorithms/matching/bipartite.ts +8 -44
  204. package/src/algorithms/shortest-path/bellman-ford.ts +1 -14
  205. package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +248 -0
  206. package/src/algorithms/shortest-path/dijkstra.ts +17 -15
  207. package/src/algorithms/traversal/bfs-unified.ts +433 -0
  208. package/src/algorithms/traversal/bfs-variants.ts +496 -0
  209. package/src/algorithms/traversal/bfs.ts +10 -228
  210. package/src/benchmark-all-algorithms.ts +548 -0
  211. package/src/clustering/hierarchical.ts +241 -251
  212. package/src/clustering/index.ts +4 -2
  213. package/src/clustering/k-core.ts +87 -39
  214. package/src/clustering/spectral.ts +185 -34
  215. package/src/core/graph.ts +4 -4
  216. package/src/flow/ford-fulkerson.ts +186 -254
  217. package/src/flow/min-cut.ts +21 -11
  218. package/src/index.ts +9 -0
  219. package/src/link-prediction/adamic-adar.ts +17 -30
  220. package/src/link-prediction/common-neighbors.ts +7 -16
  221. package/src/optimized/bit-packed.ts +340 -0
  222. package/src/optimized/csr-graph.ts +455 -0
  223. package/src/optimized/direction-optimized-bfs.ts +291 -0
  224. package/src/optimized/graph-adapter.ts +200 -0
  225. package/src/optimized/index.ts +36 -0
  226. package/src/research/grsbm.ts +580 -0
  227. package/src/research/index.ts +17 -0
  228. package/src/research/sync.ts +440 -0
  229. package/src/research/terahac.ts +514 -0
  230. package/src/types/index.ts +8 -0
  231. package/src/utils/algorithm-utilities.ts +2 -0
  232. package/src/utils/graph-converters.ts +241 -0
  233. package/src/utils/graph-utilities.ts +199 -0
  234. package/src/utils/index.ts +4 -0
  235. package/src/utils/math-utilities.ts +140 -0
  236. package/src/utils/matrix-utilities.ts +2 -0
  237. package/src/utils/optimization-helpers.ts +81 -0
@@ -1,5 +1,7 @@
1
- export * from "./hierarchical.js";
2
- export * from "./k-core.js";
1
+ export type {ClusterNode, HierarchicalClusteringResult, LinkageMethod} from "./hierarchical.js";
2
+ export {hierarchicalClustering} from "./hierarchical.js";
3
+ export type {KCoreResult} from "./k-core.js";
4
+ export {getKCore, kCoreDecomposition} from "./k-core.js";
3
5
  export type {MCLOptions, MCLResult} from "./mcl.js";
4
6
  export {calculateMCLModularity, markovClustering} from "./mcl.js";
5
7
  export type {SpectralClusteringOptions, SpectralClusteringResult} from "./spectral.js";
@@ -6,6 +6,41 @@
6
6
  * understanding graph structure.
7
7
  */
8
8
 
9
+ import type {Graph} from "../core/graph.js";
10
+
11
+ /**
12
+ * Convert Graph to adjacency set representation
13
+ */
14
+ function graphToAdjacencySet(graph: Graph): Map<string, Set<string>> {
15
+ const adjacency = new Map<string, Set<string>>();
16
+
17
+ // Initialize all nodes
18
+ for (const node of graph.nodes()) {
19
+ adjacency.set(String(node.id), new Set());
20
+ }
21
+
22
+ // Add edges
23
+ for (const edge of graph.edges()) {
24
+ const source = String(edge.source);
25
+ const target = String(edge.target);
26
+
27
+ const sourceSet = adjacency.get(source);
28
+ if (sourceSet) {
29
+ sourceSet.add(target);
30
+ }
31
+
32
+ // For undirected graphs, add reverse edge
33
+ if (!graph.isDirected) {
34
+ const targetSet = adjacency.get(target);
35
+ if (targetSet) {
36
+ targetSet.add(source);
37
+ }
38
+ }
39
+ }
40
+
41
+ return adjacency;
42
+ }
43
+
9
44
  export interface KCoreResult<T> {
10
45
  cores: Map<number, Set<T>>;
11
46
  coreness: Map<T, number>;
@@ -13,16 +48,9 @@ export interface KCoreResult<T> {
13
48
  }
14
49
 
15
50
  /**
16
- * K-Core decomposition algorithm
17
- * Finds all k-cores in the graph and assigns coreness values to nodes
18
- *
19
- * @param graph - Undirected graph as adjacency list
20
- * @returns K-core decomposition results
21
- *
22
- * Time Complexity: O(V + E)
23
- * Space Complexity: O(V)
51
+ * Internal implementation of K-Core decomposition algorithm
24
52
  */
25
- export function kCoreDecomposition<T>(
53
+ function kCoreDecompositionImpl<T>(
26
54
  graph: Map<T, Set<T>>,
27
55
  ): KCoreResult<T> {
28
56
  if (graph.size === 0) {
@@ -191,12 +219,12 @@ export function kCoreDecomposition<T>(
191
219
  * @param k - Core number
192
220
  * @returns Set of nodes in k-core or higher
193
221
  */
194
- export function getKCore<T>(
195
- graph: Map<T, Set<T>>,
222
+ export function getKCore(
223
+ graph: Graph,
196
224
  k: number,
197
- ): Set<T> {
225
+ ): Set<string> {
198
226
  const {coreness} = kCoreDecomposition(graph);
199
- const kCore = new Set<T>();
227
+ const kCore = new Set<string>();
200
228
 
201
229
  for (const [node, core] of coreness) {
202
230
  if (core >= k) {
@@ -215,17 +243,18 @@ export function getKCore<T>(
215
243
  * @param k - Core number
216
244
  * @returns K-core subgraph
217
245
  */
218
- export function getKCoreSubgraph<T>(
219
- graph: Map<T, Set<T>>,
246
+ export function getKCoreSubgraph(
247
+ graph: Graph,
220
248
  k: number,
221
- ): Map<T, Set<T>> {
249
+ ): Map<string, Set<string>> {
222
250
  const kCoreNodes = getKCore(graph, k);
223
- const subgraph = new Map<T, Set<T>>();
251
+ const adjacencySet = graphToAdjacencySet(graph);
252
+ const subgraph = new Map<string, Set<string>>();
224
253
 
225
254
  for (const node of kCoreNodes) {
226
- const neighbors = graph.get(node);
255
+ const neighbors = adjacencySet.get(node);
227
256
  if (neighbors) {
228
- const coreNeighbors = new Set<T>();
257
+ const coreNeighbors = new Set<string>();
229
258
  for (const neighbor of neighbors) {
230
259
  if (kCoreNodes.has(neighbor)) {
231
260
  coreNeighbors.add(neighbor);
@@ -245,24 +274,25 @@ export function getKCoreSubgraph<T>(
245
274
  * @param graph - Undirected graph
246
275
  * @returns Array of nodes ordered by degeneracy
247
276
  */
248
- export function degeneracyOrdering<T>(
249
- graph: Map<T, Set<T>>,
250
- ): T[] {
251
- const degree = new Map<T, number>();
252
- const remaining = new Map<T, Set<T>>();
253
- const ordering: T[] = [];
277
+ export function degeneracyOrdering(
278
+ graph: Graph,
279
+ ): string[] {
280
+ const adjacencySet = graphToAdjacencySet(graph);
281
+ const degree = new Map<string, number>();
282
+ const remaining = new Map<string, Set<string>>();
283
+ const ordering: string[] = [];
254
284
 
255
285
  // Initialize
256
- for (const [node, neighbors] of graph) {
286
+ for (const [node, neighbors] of adjacencySet) {
257
287
  degree.set(node, neighbors.size);
258
288
  remaining.set(node, new Set(neighbors));
259
289
  }
260
290
 
261
291
  // Build ordering
262
- while (ordering.length < graph.size) {
292
+ while (ordering.length < adjacencySet.size) {
263
293
  // Find minimum degree node
264
294
  let minDegree = Infinity;
265
- let minNode: T | undefined;
295
+ let minNode: string | undefined;
266
296
 
267
297
  for (const [node, deg] of degree) {
268
298
  if (!ordering.includes(node) && deg < minDegree) {
@@ -302,20 +332,22 @@ export function degeneracyOrdering<T>(
302
332
  * @param k - Truss number (k >= 2)
303
333
  * @returns Edges in k-truss
304
334
  */
305
- export function kTruss<T>(
306
- graph: Map<T, Set<T>>,
335
+ export function kTruss(
336
+ graph: Graph,
307
337
  k: number,
308
338
  ): Set<string> {
309
339
  if (k < 2) {
310
340
  throw new Error("k must be at least 2 for k-truss");
311
341
  }
312
342
 
343
+ const adjacencySet = graphToAdjacencySet(graph);
344
+
313
345
  // Count triangles for each edge
314
346
  const edgeTriangles = new Map<string, number>();
315
347
  const edges = new Set<string>();
316
348
 
317
349
  // Initialize edges
318
- for (const [u, neighbors] of graph) {
350
+ for (const [u, neighbors] of adjacencySet) {
319
351
  for (const v of neighbors) {
320
352
  if (u < v) { // Avoid duplicates
321
353
  const edge = `${String(u)},${String(v)}`;
@@ -326,10 +358,10 @@ export function kTruss<T>(
326
358
  }
327
359
 
328
360
  // Count triangles
329
- for (const [u, uNeighbors] of graph) {
361
+ for (const [u, uNeighbors] of adjacencySet) {
330
362
  for (const v of uNeighbors) {
331
363
  if (u < v) {
332
- const vNeighbors = graph.get(v);
364
+ const vNeighbors = adjacencySet.get(v);
333
365
  if (vNeighbors) {
334
366
  // Find common neighbors (triangles)
335
367
  for (const w of uNeighbors) {
@@ -390,16 +422,14 @@ export function kTruss<T>(
390
422
  }
391
423
 
392
424
  // Update triangle counts for affected edges
393
- const uNode = u as T;
394
- const vNode = v as T;
395
- const uNeighbors = graph.get(uNode);
396
- const vNeighbors = graph.get(vNode);
425
+ const uNeighbors = adjacencySet.get(u);
426
+ const vNeighbors = adjacencySet.get(v);
397
427
 
398
428
  if (uNeighbors && vNeighbors) {
399
429
  for (const w of uNeighbors) {
400
430
  if (vNeighbors.has(w)) {
401
- const edge1 = uNode < w ? `${String(uNode)},${String(w)}` : `${String(w)},${String(uNode)}`;
402
- const edge2 = vNode < w ? `${String(vNode)},${String(w)}` : `${String(w)},${String(vNode)}`;
431
+ const edge1 = u < w ? `${String(u)},${String(w)}` : `${String(w)},${String(u)}`;
432
+ const edge2 = v < w ? `${String(v)},${String(w)}` : `${String(w)},${String(v)}`;
403
433
 
404
434
  if (kTrussEdges.has(edge1)) {
405
435
  const edge1Count = edgeTriangles.get(edge1);
@@ -458,3 +488,21 @@ export function toUndirected<T>(
458
488
  return undirected;
459
489
  }
460
490
 
491
+ /**
492
+ * K-Core decomposition algorithm
493
+ * Finds all k-cores in the graph and assigns coreness values to nodes
494
+ *
495
+ * @param graph - Undirected graph - accepts Graph class or Map<T, Set<T>>
496
+ * @returns K-core decomposition results
497
+ *
498
+ * Time Complexity: O(V + E)
499
+ * Space Complexity: O(V)
500
+ */
501
+ export function kCoreDecomposition(
502
+ graph: Graph,
503
+ ): KCoreResult<string> {
504
+ // Convert Graph to adjacency set representation
505
+ const adjacencySet = graphToAdjacencySet(graph);
506
+ return kCoreDecompositionImpl(adjacencySet);
507
+ }
508
+
@@ -1,5 +1,6 @@
1
1
  import type {Graph} from "../core/graph.js";
2
2
  import type {NodeId} from "../types/index.js";
3
+ import {euclideanDistance} from "../utils/math-utilities.js";
3
4
 
4
5
  /**
5
6
  * Spectral Clustering implementation
@@ -27,6 +28,14 @@ export interface SpectralClusteringResult {
27
28
 
28
29
  /**
29
30
  * Perform spectral clustering on a graph
31
+ *
32
+ * @warning This implementation uses simplified power iteration for eigenvector
33
+ * computation with approximate eigenvalues. For production use cases requiring
34
+ * precise clustering, consider using a proper linear algebra library like ml-matrix.
35
+ *
36
+ * The approximate eigenvalues (0.1, 0.2 for second and third eigenvectors) work
37
+ * well for most graph structures but may produce suboptimal results for graphs
38
+ * with unusual spectral properties.
30
39
  */
31
40
  export function spectralClustering(
32
41
  graph: Graph,
@@ -36,8 +45,14 @@ export function spectralClustering(
36
45
  k,
37
46
  laplacianType = "normalized",
38
47
  maxIterations = 100,
48
+ tolerance = 1e-4,
39
49
  } = options;
40
50
 
51
+ // Input validation
52
+ if (k < 1 || !Number.isInteger(k)) {
53
+ throw new Error("k must be a positive integer");
54
+ }
55
+
41
56
  const nodes = Array.from(graph.nodes());
42
57
  const nodeIds = nodes.map((node) => node.id);
43
58
  const n = nodeIds.length;
@@ -59,13 +74,27 @@ export function spectralClustering(
59
74
  // Find k smallest eigenvectors
60
75
  const eigenResult = findSmallestEigenvectors(laplacianMatrix, k);
61
76
 
62
- // Normalize the eigenvector matrix row-wise (for normalized spectral clustering)
77
+ // Perform k-means clustering on the eigenvectors
78
+ // For spectral clustering, we need to transpose the eigenvector matrix
79
+ // Each row should be a data point (node) with features from the eigenvectors
80
+ const dataPoints: number[][] = [];
81
+ for (let i = 0; i < nodeIds.length; i++) {
82
+ const point: number[] = [];
83
+ for (let j = 0; j < k; j++) {
84
+ const eigenvector = eigenResult.eigenvectors[j];
85
+ if (eigenvector) {
86
+ point.push(eigenvector[i] ?? 0);
87
+ }
88
+ }
89
+ dataPoints.push(point);
90
+ }
91
+
92
+ // Normalize the data points row-wise (for normalized spectral clustering)
63
93
  if (laplacianType === "normalized") {
64
- normalizeRows(eigenResult.eigenvectors);
94
+ normalizeRows(dataPoints);
65
95
  }
66
96
 
67
- // Perform k-means clustering on the eigenvectors
68
- const kmeans = kMeansClustering(eigenResult.eigenvectors, k, maxIterations);
97
+ const kmeans = kMeansClustering(dataPoints, k, maxIterations, tolerance);
69
98
 
70
99
  // Build communities
71
100
  const communities: NodeId[][] = Array.from({length: k}, () => []);
@@ -265,32 +294,34 @@ function findSmallestEigenvectors(matrix: number[][], k: number): {
265
294
  return {eigenvalues: [], eigenvectors: []};
266
295
  }
267
296
 
268
- // For small matrices or when k >= n, use a simplified approach
269
- if (n <= 3 || k >= n) {
270
- // Create identity-like eigenvectors for simplicity
271
- const eigenvectors: number[][] = [];
272
- const eigenvalues: number[] = [];
273
-
274
- for (let i = 0; i < Math.min(k, n); i++) {
275
- const vector = Array(n).fill(0) as number[];
276
- vector[i] = 1;
277
- eigenvectors.push(vector);
278
- eigenvalues.push(i);
279
- }
297
+ // For very small matrices, use the full power iteration approach
298
+ // Remove the simplified approach that was causing issues
299
+ if (k >= n) {
300
+ // If k >= n, we still need proper eigenvectors, not identity
301
+ // Fall through to the power iteration below
302
+ }
280
303
 
281
- return {eigenvalues, eigenvectors};
304
+ // For spectral clustering, we need proper eigenvectors
305
+ // Special handling for small k values which are common in clustering
306
+ if (k <= 3 && n > k) {
307
+ return computeSmallestEigenvectorsSimple(matrix, k, n);
282
308
  }
283
309
 
284
- // For larger matrices, use power iteration to find dominant eigenvectors
285
- // This is still simplified but better than random
310
+ // For larger k, use power iteration
286
311
  const eigenvectors: number[][] = [];
287
312
  const eigenvalues: number[] = [];
288
- const maxIterations = 50;
313
+ const maxIterations = 100;
289
314
 
290
315
  for (let eigIdx = 0; eigIdx < k; eigIdx++) {
291
316
  // Initialize random vector
292
317
  let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
293
318
 
319
+ // Normalize initial vector
320
+ const initNorm = Math.sqrt(vector.reduce((sum, val) => sum + (val * val), 0));
321
+ if (initNorm > 0) {
322
+ vector = vector.map((val) => val / initNorm);
323
+ }
324
+
294
325
  // Orthogonalize against previous eigenvectors
295
326
  for (let j = 0; j < eigIdx; j++) {
296
327
  const ejVector = eigenvectors[j];
@@ -400,6 +431,7 @@ function kMeansClustering(
400
431
  data: number[][],
401
432
  k: number,
402
433
  maxIterations: number,
434
+ tolerance = 1e-4,
403
435
  ): {assignments: number[], centroids: number[][]} {
404
436
  const n = data.length;
405
437
  const d = data[0]?.length ?? 0;
@@ -464,21 +496,24 @@ function kMeansClustering(
464
496
  assignments[i] = bestCluster;
465
497
  }
466
498
 
467
- // Check for convergence
468
- let changed = false;
499
+ // Check for convergence based on assignment changes
500
+ let assignmentsChanged = false;
469
501
  for (let i = 0; i < n; i++) {
470
502
  if (assignments[i] !== oldAssignments[i]) {
471
- changed = true;
503
+ assignmentsChanged = true;
472
504
  break;
473
505
  }
474
506
  }
475
507
 
476
- if (!changed) {
508
+ if (!assignmentsChanged) {
477
509
  break;
478
510
  }
479
511
 
480
512
  oldAssignments = [... assignments];
481
513
 
514
+ // Store old centroids for tolerance-based convergence check
515
+ const oldCentroids = centroids.map((c) => [... c]);
516
+
482
517
  // Update centroids
483
518
  const counts = Array(k).fill(0) as number[];
484
519
  const sums: number[][] = Array.from({length: k}, () => Array(d).fill(0) as number[]);
@@ -518,28 +553,144 @@ function kMeansClustering(
518
553
  for (let j = 0; j < d; j++) {
519
554
  const sumVal = sumsRow[j];
520
555
  if (sumVal !== undefined) {
521
- const countVal = counts[i];
522
- if (countVal !== undefined) {
523
- centroidsRow[j] = sumVal / countVal;
556
+ const countValInner = counts[i];
557
+ if (countValInner !== undefined) {
558
+ centroidsRow[j] = sumVal / countValInner;
524
559
  }
525
560
  }
526
561
  }
527
562
  }
528
563
  }
529
564
  }
565
+
566
+ // Check for tolerance-based convergence (centroid movement)
567
+ let maxCentroidShift = 0;
568
+ for (let i = 0; i < k; i++) {
569
+ const oldCentroid = oldCentroids[i];
570
+ const newCentroid = centroids[i];
571
+ if (oldCentroid && newCentroid) {
572
+ const shift = euclideanDistance(oldCentroid, newCentroid);
573
+ if (shift > maxCentroidShift) {
574
+ maxCentroidShift = shift;
575
+ }
576
+ }
577
+ }
578
+
579
+ if (maxCentroidShift < tolerance) {
580
+ break;
581
+ }
530
582
  }
531
583
 
532
584
  return {assignments, centroids};
533
585
  }
534
586
 
535
587
  /**
536
- * Calculate Euclidean distance between two vectors
588
+ * Compute smallest eigenvectors for small k (optimized for k=2, k=3)
537
589
  */
538
- function euclideanDistance(a: number[], b: number[]): number {
539
- let sum = 0;
540
- for (let i = 0; i < a.length; i++) {
541
- const diff = (a[i] ?? 0) - (b[i] ?? 0);
542
- sum += diff * diff;
590
+ function computeSmallestEigenvectorsSimple(matrix: number[][], k: number, n: number): {
591
+ eigenvalues: number[];
592
+ eigenvectors: number[][];
593
+ } {
594
+ const eigenvectors: number[][] = [];
595
+ const eigenvalues: number[] = [];
596
+
597
+ // First eigenvector is constant (corresponds to eigenvalue 0 for connected graph)
598
+ const firstVector = Array(n).fill(1 / Math.sqrt(n)) as number[];
599
+ eigenvectors.push(firstVector);
600
+ eigenvalues.push(0);
601
+
602
+ // For k >= 2, compute the Fiedler vector (second smallest eigenvector)
603
+ if (k >= 2) {
604
+ // Use power iteration on I - L/lambda_max to find second smallest
605
+ const maxEig = 2; // For normalized Laplacian, max eigenvalue <= 2
606
+ let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
607
+
608
+ // Make orthogonal to first eigenvector
609
+ const dot1 = vector.reduce((sum, val) => sum + (val / Math.sqrt(n)), 0);
610
+ vector = vector.map((val) => val - (dot1 / Math.sqrt(n)));
611
+
612
+ // Power iteration on shifted matrix
613
+ for (let iter = 0; iter < 100; iter++) {
614
+ // Compute (I - L/maxEig) * v
615
+ const newVector = Array(n).fill(0) as number[];
616
+
617
+ // Identity part
618
+ for (let i = 0; i < n; i++) {
619
+ newVector[i] = vector[i] ?? 0;
620
+ }
621
+
622
+ // Subtract L * v / maxEig
623
+ for (let i = 0; i < n; i++) {
624
+ for (let j = 0; j < n; j++) {
625
+ const matrixVal = matrix[i]?.[j] ?? 0;
626
+ const vecVal = vector[j] ?? 0;
627
+ newVector[i] = (newVector[i] ?? 0) - ((matrixVal * vecVal) / maxEig);
628
+ }
629
+ }
630
+
631
+ // Orthogonalize against first eigenvector
632
+ const dot = newVector.reduce((sum, val) => sum + (val / Math.sqrt(n)), 0);
633
+ for (let i = 0; i < n; i++) {
634
+ newVector[i] = (newVector[i] ?? 0) - (dot / Math.sqrt(n));
635
+ }
636
+
637
+ // Normalize
638
+ const norm = Math.sqrt(newVector.reduce((sum, val) => sum + (val * val), 0));
639
+ if (norm > 1e-10) {
640
+ vector = newVector.map((val) => val / norm);
641
+ }
642
+ }
643
+
644
+ eigenvectors.push(vector);
645
+ eigenvalues.push(0.1); // Approximate
646
+ }
647
+
648
+ // For k = 3, add another eigenvector
649
+ if (k >= 3) {
650
+ let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
651
+
652
+ // Orthogonalize against previous eigenvectors
653
+ for (const prev of eigenvectors) {
654
+ const dot = vector.reduce((sum, val, idx) => sum + (val * (prev[idx] ?? 0)), 0);
655
+ vector = vector.map((val, idx) => val - (dot * (prev[idx] ?? 0)));
656
+ }
657
+
658
+ // Similar power iteration
659
+ for (let iter = 0; iter < 50; iter++) {
660
+ const newVector = Array(n).fill(0) as number[];
661
+
662
+ // Identity part
663
+ for (let i = 0; i < n; i++) {
664
+ newVector[i] = vector[i] ?? 0;
665
+ }
666
+
667
+ // Subtract L * v / 2
668
+ for (let i = 0; i < n; i++) {
669
+ for (let j = 0; j < n; j++) {
670
+ const matrixVal = matrix[i]?.[j] ?? 0;
671
+ const vecVal = vector[j] ?? 0;
672
+ newVector[i] = (newVector[i] ?? 0) - ((matrixVal * vecVal) / 2);
673
+ }
674
+ }
675
+
676
+ // Orthogonalize
677
+ for (const prev of eigenvectors) {
678
+ const dot = newVector.reduce((sum, val, idx) => sum + (val * (prev[idx] ?? 0)), 0);
679
+ for (let i = 0; i < n; i++) {
680
+ newVector[i] = (newVector[i] ?? 0) - (dot * (prev[i] ?? 0));
681
+ }
682
+ }
683
+
684
+ // Normalize
685
+ const norm = Math.sqrt(newVector.reduce((sum, val) => sum + (val * val), 0));
686
+ if (norm > 1e-10) {
687
+ vector = newVector.map((val) => val / norm);
688
+ }
689
+ }
690
+
691
+ eigenvectors.push(vector);
692
+ eigenvalues.push(0.2); // Approximate
543
693
  }
544
- return Math.sqrt(sum);
694
+
695
+ return {eigenvalues: eigenvalues.slice(0, k), eigenvectors: eigenvectors.slice(0, k)};
545
696
  }
package/src/core/graph.ts CHANGED
@@ -222,7 +222,7 @@ export class Graph {
222
222
  * Get all edges in the graph
223
223
  */
224
224
  *edges(): IterableIterator<Edge> {
225
- for (const [source, edges] of Array.from(this.adjacencyList)) {
225
+ for (const [source, edges] of this.adjacencyList) {
226
226
  for (const edge of edges.values()) {
227
227
  // For undirected graphs, only yield each edge once
228
228
  if (!this.config.directed && source > edge.target) {
@@ -300,12 +300,12 @@ export class Graph {
300
300
  const cloned = new Graph(this.config);
301
301
 
302
302
  // Copy nodes
303
- for (const node of Array.from(this.nodeMap.values())) {
303
+ for (const node of this.nodeMap.values()) {
304
304
  cloned.addNode(node.id, node.data ? {... node.data} : undefined);
305
305
  }
306
306
 
307
307
  // Copy edges
308
- for (const edge of Array.from(this.edges())) {
308
+ for (const edge of this.edges()) {
309
309
  cloned.addEdge(
310
310
  edge.source,
311
311
  edge.target,
@@ -346,7 +346,7 @@ export class Graph {
346
346
  // For undirected graphs, we need to count each edge only once
347
347
  let count = 0;
348
348
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
349
- for (const _edge of Array.from(this.edges())) {
349
+ for (const _edge of this.edges()) {
350
350
  count++;
351
351
  }
352
352
  return count;