@graphty/algorithms 1.2.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 (230) hide show
  1. package/README.md +490 -336
  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 +40 -21
  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 +1 -0
  113. package/dist/src/index.d.ts.map +1 -1
  114. package/dist/src/index.js +4 -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.map +1 -1
  143. package/dist/src/research/grsbm.js +6 -14
  144. package/dist/src/research/grsbm.js.map +1 -1
  145. package/dist/src/research/sync.d.ts.map +1 -1
  146. package/dist/src/research/sync.js +3 -29
  147. package/dist/src/research/sync.js.map +1 -1
  148. package/dist/src/research/terahac.d.ts +2 -0
  149. package/dist/src/research/terahac.d.ts.map +1 -1
  150. package/dist/src/research/terahac.js +7 -3
  151. package/dist/src/research/terahac.js.map +1 -1
  152. package/dist/src/types/index.d.ts +8 -0
  153. package/dist/src/types/index.d.ts.map +1 -1
  154. package/dist/src/utils/algorithm-utilities.d.ts +2 -0
  155. package/dist/src/utils/algorithm-utilities.d.ts.map +1 -0
  156. package/dist/src/utils/algorithm-utilities.js +2 -0
  157. package/dist/src/utils/algorithm-utilities.js.map +1 -0
  158. package/dist/src/utils/graph-converters.d.ts +79 -0
  159. package/dist/src/utils/graph-converters.d.ts.map +1 -0
  160. package/dist/src/utils/graph-converters.js +196 -0
  161. package/dist/src/utils/graph-converters.js.map +1 -0
  162. package/dist/src/utils/graph-utilities.d.ts +62 -0
  163. package/dist/src/utils/graph-utilities.d.ts.map +1 -0
  164. package/dist/src/utils/graph-utilities.js +148 -0
  165. package/dist/src/utils/graph-utilities.js.map +1 -0
  166. package/dist/src/utils/index.d.ts +5 -0
  167. package/dist/src/utils/index.d.ts.map +1 -0
  168. package/dist/src/utils/index.js +5 -0
  169. package/dist/src/utils/index.js.map +1 -0
  170. package/dist/src/utils/math-utilities.d.ts +52 -0
  171. package/dist/src/utils/math-utilities.d.ts.map +1 -0
  172. package/dist/src/utils/math-utilities.js +123 -0
  173. package/dist/src/utils/math-utilities.js.map +1 -0
  174. package/dist/src/utils/matrix-utilities.d.ts +2 -0
  175. package/dist/src/utils/matrix-utilities.d.ts.map +1 -0
  176. package/dist/src/utils/matrix-utilities.js +2 -0
  177. package/dist/src/utils/matrix-utilities.js.map +1 -0
  178. package/dist/src/utils/optimization-helpers.d.ts +33 -0
  179. package/dist/src/utils/optimization-helpers.d.ts.map +1 -0
  180. package/dist/src/utils/optimization-helpers.js +47 -0
  181. package/dist/src/utils/optimization-helpers.js.map +1 -0
  182. package/package.json +18 -3
  183. package/src/algorithms/centrality/betweenness.ts +167 -199
  184. package/src/algorithms/centrality/closeness.ts +73 -191
  185. package/src/algorithms/centrality/degree.ts +3 -3
  186. package/src/algorithms/centrality/delta-pagerank-simple.ts +366 -0
  187. package/src/algorithms/centrality/delta-pagerank.ts +435 -0
  188. package/src/algorithms/centrality/index.ts +2 -0
  189. package/src/algorithms/centrality/pagerank.ts +66 -0
  190. package/src/algorithms/community/girvan-newman.ts +52 -59
  191. package/src/algorithms/community/index.ts +2 -0
  192. package/src/algorithms/community/label-propagation.ts +64 -35
  193. package/src/algorithms/community/leiden.ts +40 -41
  194. package/src/algorithms/community/louvain-optimized.ts +445 -0
  195. package/src/algorithms/community/louvain.ts +24 -104
  196. package/src/algorithms/community/modularity-utils.ts +146 -0
  197. package/src/algorithms/matching/bipartite.ts +8 -44
  198. package/src/algorithms/shortest-path/bellman-ford.ts +1 -14
  199. package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +248 -0
  200. package/src/algorithms/shortest-path/dijkstra.ts +17 -15
  201. package/src/algorithms/traversal/bfs-unified.ts +433 -0
  202. package/src/algorithms/traversal/bfs-variants.ts +496 -0
  203. package/src/algorithms/traversal/bfs.ts +10 -228
  204. package/src/benchmark-all-algorithms.ts +548 -0
  205. package/src/clustering/hierarchical.ts +241 -251
  206. package/src/clustering/index.ts +4 -2
  207. package/src/clustering/k-core.ts +87 -39
  208. package/src/clustering/spectral.ts +44 -20
  209. package/src/core/graph.ts +4 -4
  210. package/src/flow/ford-fulkerson.ts +186 -254
  211. package/src/flow/min-cut.ts +21 -11
  212. package/src/index.ts +6 -0
  213. package/src/link-prediction/adamic-adar.ts +17 -30
  214. package/src/link-prediction/common-neighbors.ts +7 -16
  215. package/src/optimized/bit-packed.ts +340 -0
  216. package/src/optimized/csr-graph.ts +455 -0
  217. package/src/optimized/direction-optimized-bfs.ts +291 -0
  218. package/src/optimized/graph-adapter.ts +200 -0
  219. package/src/optimized/index.ts +36 -0
  220. package/src/research/grsbm.ts +7 -16
  221. package/src/research/sync.ts +3 -32
  222. package/src/research/terahac.ts +10 -2
  223. package/src/types/index.ts +8 -0
  224. package/src/utils/algorithm-utilities.ts +2 -0
  225. package/src/utils/graph-converters.ts +241 -0
  226. package/src/utils/graph-utilities.ts +199 -0
  227. package/src/utils/index.ts +4 -0
  228. package/src/utils/math-utilities.ts +140 -0
  229. package/src/utils/matrix-utilities.ts +2 -0
  230. 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;
@@ -79,7 +94,7 @@ export function spectralClustering(
79
94
  normalizeRows(dataPoints);
80
95
  }
81
96
 
82
- const kmeans = kMeansClustering(dataPoints, k, maxIterations);
97
+ const kmeans = kMeansClustering(dataPoints, k, maxIterations, tolerance);
83
98
 
84
99
  // Build communities
85
100
  const communities: NodeId[][] = Array.from({length: k}, () => []);
@@ -416,6 +431,7 @@ function kMeansClustering(
416
431
  data: number[][],
417
432
  k: number,
418
433
  maxIterations: number,
434
+ tolerance = 1e-4,
419
435
  ): {assignments: number[], centroids: number[][]} {
420
436
  const n = data.length;
421
437
  const d = data[0]?.length ?? 0;
@@ -480,21 +496,24 @@ function kMeansClustering(
480
496
  assignments[i] = bestCluster;
481
497
  }
482
498
 
483
- // Check for convergence
484
- let changed = false;
499
+ // Check for convergence based on assignment changes
500
+ let assignmentsChanged = false;
485
501
  for (let i = 0; i < n; i++) {
486
502
  if (assignments[i] !== oldAssignments[i]) {
487
- changed = true;
503
+ assignmentsChanged = true;
488
504
  break;
489
505
  }
490
506
  }
491
507
 
492
- if (!changed) {
508
+ if (!assignmentsChanged) {
493
509
  break;
494
510
  }
495
511
 
496
512
  oldAssignments = [... assignments];
497
513
 
514
+ // Store old centroids for tolerance-based convergence check
515
+ const oldCentroids = centroids.map((c) => [... c]);
516
+
498
517
  // Update centroids
499
518
  const counts = Array(k).fill(0) as number[];
500
519
  const sums: number[][] = Array.from({length: k}, () => Array(d).fill(0) as number[]);
@@ -534,30 +553,35 @@ function kMeansClustering(
534
553
  for (let j = 0; j < d; j++) {
535
554
  const sumVal = sumsRow[j];
536
555
  if (sumVal !== undefined) {
537
- const countVal = counts[i];
538
- if (countVal !== undefined) {
539
- centroidsRow[j] = sumVal / countVal;
556
+ const countValInner = counts[i];
557
+ if (countValInner !== undefined) {
558
+ centroidsRow[j] = sumVal / countValInner;
540
559
  }
541
560
  }
542
561
  }
543
562
  }
544
563
  }
545
564
  }
546
- }
547
565
 
548
- return {assignments, centroids};
549
- }
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
+ }
550
578
 
551
- /**
552
- * Calculate Euclidean distance between two vectors
553
- */
554
- function euclideanDistance(a: number[], b: number[]): number {
555
- let sum = 0;
556
- for (let i = 0; i < a.length; i++) {
557
- const diff = (a[i] ?? 0) - (b[i] ?? 0);
558
- sum += diff * diff;
579
+ if (maxCentroidShift < tolerance) {
580
+ break;
581
+ }
559
582
  }
560
- return Math.sqrt(sum);
583
+
584
+ return {assignments, centroids};
561
585
  }
562
586
 
563
587
  /**
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;