@graphty/algorithms 2.1.2 → 2.2.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.
- package/README.md +139 -50
- package/dist/algorithms.js +15260 -10390
- package/dist/algorithms.js.map +1 -1
- package/dist/algorithms.standalone.js +26675 -21542
- package/dist/algorithms.standalone.js.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.d.ts +18 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +40 -1
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.d.ts +20 -0
- package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +93 -8
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.d.ts +9 -0
- package/dist/src/algorithms/centrality/degree.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +18 -0
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.d.ts +10 -0
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.js +29 -0
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -1
- package/dist/src/algorithms/centrality/hits.d.ts +9 -0
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/hits.js +23 -0
- package/dist/src/algorithms/centrality/hits.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +9 -0
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/katz.js +31 -0
- package/dist/src/algorithms/centrality/katz.js.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.d.ts +13 -13
- package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.js +47 -10
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman-legacy.d.ts +27 -0
- package/dist/src/algorithms/community/girvan-newman-legacy.d.ts.map +1 -0
- package/dist/src/algorithms/community/girvan-newman-legacy.js +335 -0
- package/dist/src/algorithms/community/girvan-newman-legacy.js.map +1 -0
- package/dist/src/algorithms/community/girvan-newman.d.ts +5 -3
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.js +34 -308
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
- package/dist/src/algorithms/community/label-propagation.d.ts +9 -6
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
- package/dist/src/algorithms/community/label-propagation.js +24 -110
- package/dist/src/algorithms/community/label-propagation.js.map +1 -1
- package/dist/src/algorithms/components/connected.d.ts.map +1 -1
- package/dist/src/algorithms/components/connected.js +28 -77
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -1
- package/dist/src/algorithms/mst/kruskal.js +59 -3
- package/dist/src/algorithms/mst/kruskal.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.js +46 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +27 -0
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +15 -5
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +97 -107
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.d.ts +2 -12
- package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.js +47 -308
- package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.js +5 -25
- package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs.d.ts +0 -3
- package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +0 -4
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +33 -154
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/clustering/hierarchical-legacy.d.ts +56 -0
- package/dist/src/clustering/hierarchical-legacy.d.ts.map +1 -0
- package/dist/src/clustering/hierarchical-legacy.js +441 -0
- package/dist/src/clustering/hierarchical-legacy.js.map +1 -0
- package/dist/src/clustering/hierarchical.d.ts +7 -39
- package/dist/src/clustering/hierarchical.d.ts.map +1 -1
- package/dist/src/clustering/hierarchical.js +56 -417
- package/dist/src/clustering/hierarchical.js.map +1 -1
- package/dist/src/clustering/k-core-legacy.d.ts +30 -0
- package/dist/src/clustering/k-core-legacy.d.ts.map +1 -0
- package/dist/src/clustering/k-core-legacy.js +191 -0
- package/dist/src/clustering/k-core-legacy.js.map +1 -0
- package/dist/src/clustering/k-core.d.ts +8 -7
- package/dist/src/clustering/k-core.d.ts.map +1 -1
- package/dist/src/clustering/k-core.js +33 -174
- package/dist/src/clustering/k-core.js.map +1 -1
- package/dist/src/clustering/mcl-legacy.d.ts +42 -0
- package/dist/src/clustering/mcl-legacy.d.ts.map +1 -0
- package/dist/src/clustering/mcl-legacy.js +386 -0
- package/dist/src/clustering/mcl-legacy.js.map +1 -0
- package/dist/src/clustering/mcl.d.ts +9 -32
- package/dist/src/clustering/mcl.d.ts.map +1 -1
- package/dist/src/clustering/mcl.js +48 -374
- package/dist/src/clustering/mcl.js.map +1 -1
- package/dist/src/index.d.ts +29 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexed/accelerator.d.ts +141 -11
- package/dist/src/indexed/accelerator.d.ts.map +1 -1
- package/dist/src/indexed/accelerator.js +319 -5
- package/dist/src/indexed/accelerator.js.map +1 -1
- package/dist/src/indexed/all-pairs.d.ts +59 -0
- package/dist/src/indexed/all-pairs.d.ts.map +1 -0
- package/dist/src/indexed/all-pairs.js +274 -0
- package/dist/src/indexed/all-pairs.js.map +1 -0
- package/dist/src/indexed/bellman-ford.d.ts +25 -0
- package/dist/src/indexed/bellman-ford.d.ts.map +1 -0
- package/dist/src/indexed/bellman-ford.js +59 -0
- package/dist/src/indexed/bellman-ford.js.map +1 -0
- package/dist/src/indexed/betweenness.d.ts +85 -0
- package/dist/src/indexed/betweenness.d.ts.map +1 -0
- package/dist/src/indexed/betweenness.js +188 -0
- package/dist/src/indexed/betweenness.js.map +1 -0
- package/dist/src/indexed/bfs.d.ts +69 -2
- package/dist/src/indexed/bfs.d.ts.map +1 -1
- package/dist/src/indexed/bfs.js +148 -1
- package/dist/src/indexed/bfs.js.map +1 -1
- package/dist/src/indexed/bipartite.d.ts +37 -0
- package/dist/src/indexed/bipartite.d.ts.map +1 -0
- package/dist/src/indexed/bipartite.js +54 -0
- package/dist/src/indexed/bipartite.js.map +1 -0
- package/dist/src/indexed/closeness.d.ts +49 -0
- package/dist/src/indexed/closeness.d.ts.map +1 -0
- package/dist/src/indexed/closeness.js +127 -0
- package/dist/src/indexed/closeness.js.map +1 -0
- package/dist/src/indexed/common-neighbors.d.ts +14 -1
- package/dist/src/indexed/common-neighbors.d.ts.map +1 -1
- package/dist/src/indexed/common-neighbors.js +24 -14
- package/dist/src/indexed/common-neighbors.js.map +1 -1
- package/dist/src/indexed/degree.d.ts +19 -0
- package/dist/src/indexed/degree.d.ts.map +1 -0
- package/dist/src/indexed/degree.js +45 -0
- package/dist/src/indexed/degree.js.map +1 -0
- package/dist/src/indexed/delta-pagerank.d.ts +145 -0
- package/dist/src/indexed/delta-pagerank.d.ts.map +1 -0
- package/dist/src/indexed/delta-pagerank.js +428 -0
- package/dist/src/indexed/delta-pagerank.js.map +1 -0
- package/dist/src/indexed/dfs.d.ts +55 -0
- package/dist/src/indexed/dfs.d.ts.map +1 -0
- package/dist/src/indexed/dfs.js +146 -0
- package/dist/src/indexed/dfs.js.map +1 -0
- package/dist/src/indexed/eigenvector.d.ts +48 -0
- package/dist/src/indexed/eigenvector.d.ts.map +1 -0
- package/dist/src/indexed/eigenvector.js +162 -0
- package/dist/src/indexed/eigenvector.js.map +1 -0
- package/dist/src/indexed/facade.d.ts +109 -0
- package/dist/src/indexed/facade.d.ts.map +1 -0
- package/dist/src/indexed/facade.js +204 -0
- package/dist/src/indexed/facade.js.map +1 -0
- package/dist/src/indexed/flow.d.ts +120 -0
- package/dist/src/indexed/flow.d.ts.map +1 -0
- package/dist/src/indexed/flow.js +377 -0
- package/dist/src/indexed/flow.js.map +1 -0
- package/dist/src/indexed/girvan-newman.d.ts +42 -0
- package/dist/src/indexed/girvan-newman.d.ts.map +1 -0
- package/dist/src/indexed/girvan-newman.js +90 -0
- package/dist/src/indexed/girvan-newman.js.map +1 -0
- package/dist/src/indexed/grsbm.d.ts +85 -0
- package/dist/src/indexed/grsbm.d.ts.map +1 -0
- package/dist/src/indexed/grsbm.js +287 -0
- package/dist/src/indexed/grsbm.js.map +1 -0
- package/dist/src/indexed/hierarchical.d.ts +69 -0
- package/dist/src/indexed/hierarchical.d.ts.map +1 -0
- package/dist/src/indexed/hierarchical.js +245 -0
- package/dist/src/indexed/hierarchical.js.map +1 -0
- package/dist/src/indexed/index.d.ts +29 -3
- package/dist/src/indexed/index.d.ts.map +1 -1
- package/dist/src/indexed/index.js +29 -3
- package/dist/src/indexed/index.js.map +1 -1
- package/dist/src/indexed/isomorphism.d.ts +42 -0
- package/dist/src/indexed/isomorphism.d.ts.map +1 -0
- package/dist/src/indexed/isomorphism.js +269 -0
- package/dist/src/indexed/isomorphism.js.map +1 -0
- package/dist/src/indexed/label-propagation.d.ts +134 -0
- package/dist/src/indexed/label-propagation.d.ts.map +1 -0
- package/dist/src/indexed/label-propagation.js +486 -0
- package/dist/src/indexed/label-propagation.js.map +1 -0
- package/dist/src/indexed/leiden.d.ts +52 -0
- package/dist/src/indexed/leiden.d.ts.map +1 -0
- package/dist/src/indexed/leiden.js +384 -0
- package/dist/src/indexed/leiden.js.map +1 -0
- package/dist/src/indexed/link-prediction.d.ts +147 -0
- package/dist/src/indexed/link-prediction.d.ts.map +1 -0
- package/dist/src/indexed/link-prediction.js +317 -0
- package/dist/src/indexed/link-prediction.js.map +1 -0
- package/dist/src/indexed/markov.d.ts +54 -0
- package/dist/src/indexed/markov.d.ts.map +1 -0
- package/dist/src/indexed/markov.js +284 -0
- package/dist/src/indexed/markov.js.map +1 -0
- package/dist/src/indexed/matching.d.ts +53 -0
- package/dist/src/indexed/matching.d.ts.map +1 -0
- package/dist/src/indexed/matching.js +139 -0
- package/dist/src/indexed/matching.js.map +1 -0
- package/dist/src/indexed/min-cut.d.ts +48 -0
- package/dist/src/indexed/min-cut.d.ts.map +1 -0
- package/dist/src/indexed/min-cut.js +175 -0
- package/dist/src/indexed/min-cut.js.map +1 -0
- package/dist/src/indexed/modularity.d.ts +34 -0
- package/dist/src/indexed/modularity.d.ts.map +1 -0
- package/dist/src/indexed/modularity.js +74 -0
- package/dist/src/indexed/modularity.js.map +1 -0
- package/dist/src/indexed/mst.d.ts +29 -1
- package/dist/src/indexed/mst.d.ts.map +1 -1
- package/dist/src/indexed/mst.js +62 -0
- package/dist/src/indexed/mst.js.map +1 -1
- package/dist/src/indexed/pagerank.d.ts +29 -6
- package/dist/src/indexed/pagerank.d.ts.map +1 -1
- package/dist/src/indexed/pagerank.js +107 -11
- package/dist/src/indexed/pagerank.js.map +1 -1
- package/dist/src/indexed/point-to-point.d.ts +53 -0
- package/dist/src/indexed/point-to-point.d.ts.map +1 -0
- package/dist/src/indexed/point-to-point.js +173 -0
- package/dist/src/indexed/point-to-point.js.map +1 -0
- package/dist/src/indexed/scc.d.ts +53 -0
- package/dist/src/indexed/scc.d.ts.map +1 -0
- package/dist/src/indexed/scc.js +110 -0
- package/dist/src/indexed/scc.js.map +1 -0
- package/dist/src/indexed/spectral.d.ts +61 -0
- package/dist/src/indexed/spectral.d.ts.map +1 -0
- package/dist/src/indexed/spectral.js +476 -0
- package/dist/src/indexed/spectral.js.map +1 -0
- package/dist/src/indexed/structures/bit-set.d.ts +45 -0
- package/dist/src/indexed/structures/bit-set.d.ts.map +1 -0
- package/dist/src/indexed/structures/bit-set.js +57 -0
- package/dist/src/indexed/structures/bit-set.js.map +1 -0
- package/dist/src/indexed/structures/max-heap.d.ts +52 -0
- package/dist/src/indexed/structures/max-heap.d.ts.map +1 -0
- package/dist/src/indexed/structures/max-heap.js +66 -0
- package/dist/src/indexed/structures/max-heap.js.map +1 -0
- package/dist/src/indexed/structures/min-heap.d.ts +30 -1
- package/dist/src/indexed/structures/min-heap.d.ts.map +1 -1
- package/dist/src/indexed/structures/min-heap.js +46 -6
- package/dist/src/indexed/structures/min-heap.js.map +1 -1
- package/dist/src/indexed/structures/ring-queue.d.ts +41 -0
- package/dist/src/indexed/structures/ring-queue.d.ts.map +1 -0
- package/dist/src/indexed/structures/ring-queue.js +64 -0
- package/dist/src/indexed/structures/ring-queue.js.map +1 -0
- package/dist/src/indexed/sync.d.ts +50 -0
- package/dist/src/indexed/sync.d.ts.map +1 -0
- package/dist/src/indexed/sync.js +175 -0
- package/dist/src/indexed/sync.js.map +1 -0
- package/dist/src/indexed/terahac.d.ts +58 -0
- package/dist/src/indexed/terahac.d.ts.map +1 -0
- package/dist/src/indexed/terahac.js +244 -0
- package/dist/src/indexed/terahac.js.map +1 -0
- package/dist/src/indexed/to-snapshot.d.ts +45 -2
- package/dist/src/indexed/to-snapshot.d.ts.map +1 -1
- package/dist/src/indexed/to-snapshot.js +140 -6
- package/dist/src/indexed/to-snapshot.js.map +1 -1
- package/dist/src/link-prediction/common-neighbors-legacy.d.ts +71 -0
- package/dist/src/link-prediction/common-neighbors-legacy.d.ts.map +1 -0
- package/dist/src/link-prediction/common-neighbors-legacy.js +172 -0
- package/dist/src/link-prediction/common-neighbors-legacy.js.map +1 -0
- package/dist/src/link-prediction/common-neighbors.d.ts +11 -19
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
- package/dist/src/link-prediction/common-neighbors.js +56 -123
- package/dist/src/link-prediction/common-neighbors.js.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.d.ts +6 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.js +6 -0
- package/dist/src/optimized/direction-optimized-bfs.js.map +1 -1
- package/dist/src/research/grsbm-legacy.d.ts +82 -0
- package/dist/src/research/grsbm-legacy.d.ts.map +1 -0
- package/dist/src/research/grsbm-legacy.js +416 -0
- package/dist/src/research/grsbm-legacy.js.map +1 -0
- package/dist/src/research/grsbm.d.ts +9 -66
- package/dist/src/research/grsbm.d.ts.map +1 -1
- package/dist/src/research/grsbm.js +67 -391
- package/dist/src/research/grsbm.js.map +1 -1
- package/dist/src/research/sync-legacy.d.ts +48 -0
- package/dist/src/research/sync-legacy.d.ts.map +1 -0
- package/dist/src/research/sync-legacy.js +337 -0
- package/dist/src/research/sync-legacy.js.map +1 -0
- package/dist/src/research/sync.d.ts +10 -32
- package/dist/src/research/sync.d.ts.map +1 -1
- package/dist/src/research/sync.js +28 -316
- package/dist/src/research/sync.js.map +1 -1
- package/package.json +4 -3
- package/src/algorithms/centrality/betweenness.ts +51 -1
- package/src/algorithms/centrality/closeness.ts +112 -12
- package/src/algorithms/centrality/degree.ts +19 -0
- package/src/algorithms/centrality/eigenvector.ts +34 -0
- package/src/algorithms/centrality/hits.ts +24 -0
- package/src/algorithms/centrality/katz.ts +32 -0
- package/src/algorithms/centrality/pagerank.ts +53 -13
- package/src/algorithms/community/girvan-newman-legacy.ts +414 -0
- package/src/algorithms/community/girvan-newman.ts +36 -386
- package/src/algorithms/community/label-propagation.ts +24 -133
- package/src/algorithms/components/connected.ts +28 -95
- package/src/algorithms/mst/kruskal.ts +65 -4
- package/src/algorithms/shortest-path/bellman-ford.ts +49 -0
- package/src/algorithms/shortest-path/dijkstra.ts +29 -0
- package/src/algorithms/shortest-path/floyd-warshall.ts +106 -136
- package/src/algorithms/traversal/bfs-unified.ts +52 -366
- package/src/algorithms/traversal/bfs-variants.ts +5 -28
- package/src/algorithms/traversal/bfs.ts +0 -4
- package/src/algorithms/traversal/dfs.ts +39 -182
- package/src/clustering/hierarchical-legacy.ts +551 -0
- package/src/clustering/hierarchical.ts +64 -520
- package/src/clustering/k-core-legacy.ts +229 -0
- package/src/clustering/k-core.ts +38 -209
- package/src/clustering/mcl-legacy.ts +498 -0
- package/src/clustering/mcl.ts +46 -479
- package/src/index.ts +90 -3
- package/src/indexed/accelerator.ts +502 -16
- package/src/indexed/all-pairs.ts +342 -0
- package/src/indexed/bellman-ford.ts +72 -0
- package/src/indexed/betweenness.ts +258 -0
- package/src/indexed/bfs.ts +189 -3
- package/src/indexed/bipartite.ts +77 -0
- package/src/indexed/closeness.ts +160 -0
- package/src/indexed/common-neighbors.ts +31 -14
- package/src/indexed/degree.ts +55 -0
- package/src/indexed/delta-pagerank.ts +513 -0
- package/src/indexed/dfs.ts +189 -0
- package/src/indexed/eigenvector.ts +202 -0
- package/src/indexed/facade.ts +229 -0
- package/src/indexed/flow.ts +500 -0
- package/src/indexed/girvan-newman.ts +120 -0
- package/src/indexed/grsbm.ts +390 -0
- package/src/indexed/hierarchical.ts +295 -0
- package/src/indexed/index.ts +94 -3
- package/src/indexed/isomorphism.ts +305 -0
- package/src/indexed/label-propagation.ts +582 -0
- package/src/indexed/leiden.ts +436 -0
- package/src/indexed/link-prediction.ts +399 -0
- package/src/indexed/markov.ts +324 -0
- package/src/indexed/matching.ts +192 -0
- package/src/indexed/min-cut.ts +201 -0
- package/src/indexed/modularity.ts +81 -0
- package/src/indexed/mst.ts +79 -1
- package/src/indexed/pagerank.ts +128 -15
- package/src/indexed/point-to-point.ts +220 -0
- package/src/indexed/scc.ts +132 -0
- package/src/indexed/spectral.ts +550 -0
- package/src/indexed/structures/bit-set.ts +65 -0
- package/src/indexed/structures/max-heap.ts +74 -0
- package/src/indexed/structures/min-heap.ts +51 -6
- package/src/indexed/structures/ring-queue.ts +70 -0
- package/src/indexed/sync.ts +229 -0
- package/src/indexed/terahac.ts +290 -0
- package/src/indexed/to-snapshot.ts +156 -6
- package/src/link-prediction/common-neighbors-legacy.ts +252 -0
- package/src/link-prediction/common-neighbors.ts +65 -168
- package/src/optimized/direction-optimized-bfs.ts +6 -1
- package/src/research/grsbm-legacy.ts +586 -0
- package/src/research/grsbm.ts +76 -555
- package/src/research/sync-legacy.ts +456 -0
- package/src/research/sync.ts +30 -431
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import type { Graph } from "../core/graph.js";
|
|
2
|
+
import type { NodeId } from "../types/index.js";
|
|
3
|
+
import { euclideanDistance, SeededRandom } from "../utils/math-utilities.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration options for the SynC (Synergistic Deep Graph Clustering) algorithm
|
|
7
|
+
*/
|
|
8
|
+
export interface SynCConfig {
|
|
9
|
+
/** Number of clusters to find */
|
|
10
|
+
numClusters: number;
|
|
11
|
+
/** Maximum number of iterations for convergence */
|
|
12
|
+
maxIterations?: number;
|
|
13
|
+
/** Convergence tolerance */
|
|
14
|
+
tolerance?: number;
|
|
15
|
+
/** Random seed for reproducibility */
|
|
16
|
+
seed?: number;
|
|
17
|
+
/** Learning rate for optimization */
|
|
18
|
+
learningRate?: number;
|
|
19
|
+
/** Regularization parameter */
|
|
20
|
+
lambda?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Result of the SynC clustering algorithm
|
|
25
|
+
*/
|
|
26
|
+
export interface SynCResult {
|
|
27
|
+
/** Cluster assignments for each node */
|
|
28
|
+
clusters: Map<NodeId, number>;
|
|
29
|
+
/** Final loss value */
|
|
30
|
+
loss: number;
|
|
31
|
+
/** Number of iterations performed */
|
|
32
|
+
iterations: number;
|
|
33
|
+
/** Node embeddings learned by the algorithm */
|
|
34
|
+
embeddings: Map<NodeId, number[]>;
|
|
35
|
+
/** Whether the algorithm converged */
|
|
36
|
+
converged: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Synergistic Deep Graph Clustering (SynC) Algorithm
|
|
41
|
+
*
|
|
42
|
+
* This algorithm combines representation learning with structure augmentation
|
|
43
|
+
* for improved clustering performance on graphs. It jointly optimizes node
|
|
44
|
+
* embeddings and cluster assignments while preserving graph structure.
|
|
45
|
+
*
|
|
46
|
+
* Based on: "Synergistic Deep Graph Clustering" (arXiv:2406.15797, June 2024)
|
|
47
|
+
* @param graph - Input graph to cluster
|
|
48
|
+
* @param config - Configuration options
|
|
49
|
+
* @returns Clustering result with assignments and embeddings
|
|
50
|
+
*/
|
|
51
|
+
export function syncClustering(graph: Graph, config: SynCConfig): SynCResult {
|
|
52
|
+
const { numClusters, maxIterations = 100, tolerance = 1e-6, seed = 42, learningRate = 0.01, lambda = 0.1 } = config;
|
|
53
|
+
|
|
54
|
+
// Set random seed for reproducibility (save original)
|
|
55
|
+
const originalRandom = Math.random;
|
|
56
|
+
const rng = SeededRandom.createGenerator(seed);
|
|
57
|
+
Math.random = rng;
|
|
58
|
+
|
|
59
|
+
const nodes = Array.from(graph.nodes());
|
|
60
|
+
const nodeCount = nodes.length;
|
|
61
|
+
|
|
62
|
+
if (nodeCount === 0) {
|
|
63
|
+
return {
|
|
64
|
+
clusters: new Map(),
|
|
65
|
+
loss: 0,
|
|
66
|
+
iterations: 0,
|
|
67
|
+
embeddings: new Map(),
|
|
68
|
+
converged: true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (numClusters <= 0 || numClusters > nodeCount) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Invalid number of clusters: ${String(numClusters)}. Must be between 1 and ${String(nodeCount)}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Initialize node embeddings (simplified version using graph features)
|
|
79
|
+
const embeddingDim = Math.min(64, nodeCount);
|
|
80
|
+
const embeddings = new Map<NodeId, number[]>();
|
|
81
|
+
|
|
82
|
+
// Initialize embeddings based on node features
|
|
83
|
+
for (const node of nodes) {
|
|
84
|
+
const embedding = initializeNodeEmbedding(graph, node.id, embeddingDim);
|
|
85
|
+
embeddings.set(node.id, embedding);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Initialize cluster centers
|
|
89
|
+
const clusterCenters = initializeClusterCenters(embeddings, numClusters);
|
|
90
|
+
|
|
91
|
+
let previousLoss = Infinity;
|
|
92
|
+
let iterations = 0;
|
|
93
|
+
let converged = false;
|
|
94
|
+
|
|
95
|
+
for (iterations = 0; iterations < maxIterations; iterations++) {
|
|
96
|
+
// E-step: Assign nodes to clusters
|
|
97
|
+
const clusters = new Map<NodeId, number>();
|
|
98
|
+
|
|
99
|
+
for (const node of nodes) {
|
|
100
|
+
const nodeEmbedding = embeddings.get(node.id);
|
|
101
|
+
if (!nodeEmbedding) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let bestCluster = 0;
|
|
106
|
+
let minDistance = Infinity;
|
|
107
|
+
|
|
108
|
+
for (let k = 0; k < numClusters; k++) {
|
|
109
|
+
const center = clusterCenters[k];
|
|
110
|
+
if (!center) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const distance = euclideanDistance(nodeEmbedding, center);
|
|
115
|
+
if (distance < minDistance) {
|
|
116
|
+
minDistance = distance;
|
|
117
|
+
bestCluster = k;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
clusters.set(node.id, bestCluster);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// M-step: Update embeddings and cluster centers
|
|
125
|
+
updateEmbeddings(graph, embeddings, clusters, learningRate, lambda);
|
|
126
|
+
updateClusterCenters(embeddings, clusters, clusterCenters, numClusters);
|
|
127
|
+
|
|
128
|
+
// Calculate current loss
|
|
129
|
+
const currentLoss = calculateLoss(graph, embeddings, clusters, clusterCenters, lambda);
|
|
130
|
+
|
|
131
|
+
// Check for convergence
|
|
132
|
+
if (Math.abs(previousLoss - currentLoss) < tolerance) {
|
|
133
|
+
converged = true;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
previousLoss = currentLoss;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Final cluster assignment
|
|
141
|
+
const finalClusters = new Map<NodeId, number>();
|
|
142
|
+
for (const node of nodes) {
|
|
143
|
+
const nodeEmbedding = embeddings.get(node.id);
|
|
144
|
+
if (!nodeEmbedding) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let bestCluster = 0;
|
|
149
|
+
let minDistance = Infinity;
|
|
150
|
+
|
|
151
|
+
for (let k = 0; k < numClusters; k++) {
|
|
152
|
+
const center = clusterCenters[k];
|
|
153
|
+
if (!center) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const distance = euclideanDistance(nodeEmbedding, center);
|
|
158
|
+
if (distance < minDistance) {
|
|
159
|
+
minDistance = distance;
|
|
160
|
+
bestCluster = k;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
finalClusters.set(node.id, bestCluster);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Restore original random function
|
|
168
|
+
Math.random = originalRandom;
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
clusters: finalClusters,
|
|
172
|
+
loss: previousLoss,
|
|
173
|
+
iterations: iterations + 1,
|
|
174
|
+
embeddings,
|
|
175
|
+
converged,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Initialize node embedding based on graph structure and features
|
|
181
|
+
* @param graph - The input graph
|
|
182
|
+
* @param nodeId - The node ID to initialize embedding for
|
|
183
|
+
* @param dim - The embedding dimension
|
|
184
|
+
* @returns The initialized embedding vector
|
|
185
|
+
*/
|
|
186
|
+
function initializeNodeEmbedding(graph: Graph, nodeId: NodeId, dim: number): number[] {
|
|
187
|
+
const embedding = new Array<number>(dim).fill(0);
|
|
188
|
+
|
|
189
|
+
// Use node degree as a base feature
|
|
190
|
+
const degree = graph.degree(nodeId);
|
|
191
|
+
const normalizedDegree = degree / Math.max(1, graph.nodeCount - 1);
|
|
192
|
+
|
|
193
|
+
// Initialize with small random values influenced by graph structure
|
|
194
|
+
for (let i = 0; i < dim; i++) {
|
|
195
|
+
embedding[i] = (Math.random() - 0.5) * 0.1 + normalizedDegree * 0.1;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return embedding;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Initialize cluster centers using k-means++ style initialization
|
|
203
|
+
* @param embeddings - Map of node IDs to embedding vectors
|
|
204
|
+
* @param numClusters - Number of cluster centers to initialize
|
|
205
|
+
* @returns Array of initialized cluster center vectors
|
|
206
|
+
*/
|
|
207
|
+
function initializeClusterCenters(embeddings: Map<NodeId, number[]>, numClusters: number): number[][] {
|
|
208
|
+
const embeddingArray = Array.from(embeddings.values());
|
|
209
|
+
const centers: number[][] = [];
|
|
210
|
+
|
|
211
|
+
// Choose first center randomly
|
|
212
|
+
const firstCenter = embeddingArray[Math.floor(Math.random() * embeddingArray.length)];
|
|
213
|
+
if (firstCenter) {
|
|
214
|
+
centers.push([...firstCenter] as number[]);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Choose remaining centers using k-means++ initialization
|
|
218
|
+
for (let k = 1; k < numClusters; k++) {
|
|
219
|
+
const distances: number[] = [];
|
|
220
|
+
let totalDistance = 0;
|
|
221
|
+
|
|
222
|
+
for (const embedding of embeddingArray) {
|
|
223
|
+
let minDistance = Infinity;
|
|
224
|
+
for (const center of centers) {
|
|
225
|
+
const distance = euclideanDistance(embedding, center);
|
|
226
|
+
minDistance = Math.min(minDistance, distance);
|
|
227
|
+
}
|
|
228
|
+
distances.push(minDistance * minDistance);
|
|
229
|
+
totalDistance += minDistance * minDistance;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Choose next center with probability proportional to squared distance
|
|
233
|
+
let randomValue = Math.random() * totalDistance;
|
|
234
|
+
for (let i = 0; i < embeddingArray.length; i++) {
|
|
235
|
+
const distanceValue = distances[i];
|
|
236
|
+
if (distanceValue !== undefined) {
|
|
237
|
+
randomValue -= distanceValue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (randomValue <= 0) {
|
|
241
|
+
const newCenter = embeddingArray[i];
|
|
242
|
+
if (newCenter) {
|
|
243
|
+
centers.push([...newCenter] as number[]);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return centers;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Update node embeddings using gradient descent
|
|
256
|
+
* @param graph - The input graph
|
|
257
|
+
* @param embeddings - Map of node IDs to embedding vectors
|
|
258
|
+
* @param _clusters - Map of node IDs to cluster assignments (reserved for future use)
|
|
259
|
+
* @param learningRate - Learning rate for gradient descent
|
|
260
|
+
* @param lambda - Regularization parameter
|
|
261
|
+
*/
|
|
262
|
+
function updateEmbeddings(
|
|
263
|
+
graph: Graph,
|
|
264
|
+
embeddings: Map<NodeId, number[]>,
|
|
265
|
+
_clusters: Map<NodeId, number>,
|
|
266
|
+
learningRate: number,
|
|
267
|
+
lambda: number,
|
|
268
|
+
): void {
|
|
269
|
+
const gradients = new Map<NodeId, number[]>();
|
|
270
|
+
|
|
271
|
+
// Initialize gradients
|
|
272
|
+
for (const [nodeId, embedding] of embeddings) {
|
|
273
|
+
gradients.set(nodeId, new Array<number>(embedding.length).fill(0));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Calculate gradients based on graph structure (simplified)
|
|
277
|
+
for (const node of graph.nodes()) {
|
|
278
|
+
const nodeId = node.id;
|
|
279
|
+
const nodeEmbedding = embeddings.get(nodeId);
|
|
280
|
+
if (!nodeEmbedding) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const gradient = gradients.get(nodeId);
|
|
285
|
+
if (!gradient) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Neighbor reconstruction loss gradient
|
|
290
|
+
for (const neighborId of graph.neighbors(nodeId)) {
|
|
291
|
+
const neighborEmbedding = embeddings.get(neighborId);
|
|
292
|
+
if (!neighborEmbedding) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const diff = nodeEmbedding.map((val: number, i: number) => val - (neighborEmbedding[i] ?? 0));
|
|
297
|
+
|
|
298
|
+
for (let i = 0; i < gradient.length; i++) {
|
|
299
|
+
const gradVal = gradient[i];
|
|
300
|
+
const diffVal = diff[i];
|
|
301
|
+
if (gradVal !== undefined && diffVal !== undefined) {
|
|
302
|
+
gradient[i] = gradVal + lambda * diffVal;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Regularization gradient
|
|
308
|
+
for (let i = 0; i < gradient.length; i++) {
|
|
309
|
+
const gradVal = gradient[i];
|
|
310
|
+
const nodeVal = nodeEmbedding[i];
|
|
311
|
+
if (gradVal !== undefined && nodeVal !== undefined) {
|
|
312
|
+
gradient[i] = gradVal + lambda * nodeVal;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Update embeddings
|
|
318
|
+
for (const [nodeId, embedding] of embeddings) {
|
|
319
|
+
const gradient = gradients.get(nodeId);
|
|
320
|
+
if (!gradient) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
325
|
+
const embVal = embedding[i];
|
|
326
|
+
const gradVal = gradient[i];
|
|
327
|
+
if (embVal !== undefined && gradVal !== undefined) {
|
|
328
|
+
embedding[i] = embVal - learningRate * gradVal;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Update cluster centers based on current assignments
|
|
336
|
+
* @param embeddings - Map of node IDs to embedding vectors
|
|
337
|
+
* @param clusters - Map of node IDs to cluster assignments
|
|
338
|
+
* @param clusterCenters - Array of cluster center vectors to update
|
|
339
|
+
* @param numClusters - Number of clusters
|
|
340
|
+
*/
|
|
341
|
+
function updateClusterCenters(
|
|
342
|
+
embeddings: Map<NodeId, number[]>,
|
|
343
|
+
clusters: Map<NodeId, number>,
|
|
344
|
+
clusterCenters: number[][],
|
|
345
|
+
numClusters: number,
|
|
346
|
+
): void {
|
|
347
|
+
const dimensions = clusterCenters[0]?.length ?? 0;
|
|
348
|
+
const clusterSums: number[][] = Array.from(
|
|
349
|
+
{ length: numClusters },
|
|
350
|
+
() => new Array(dimensions).fill(0) as number[],
|
|
351
|
+
);
|
|
352
|
+
const clusterCounts = new Array<number>(numClusters).fill(0);
|
|
353
|
+
|
|
354
|
+
// Sum embeddings for each cluster
|
|
355
|
+
for (const [nodeId, clusterIdx] of clusters) {
|
|
356
|
+
const embedding = embeddings.get(nodeId);
|
|
357
|
+
if (!embedding) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
362
|
+
const clusterSum = clusterSums[clusterIdx];
|
|
363
|
+
if (!clusterSum) {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const sumVal = clusterSum[i];
|
|
368
|
+
const embVal = embedding[i];
|
|
369
|
+
if (sumVal !== undefined && embVal !== undefined) {
|
|
370
|
+
clusterSum[i] = sumVal + embVal;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (clusterCounts[clusterIdx] !== undefined) {
|
|
374
|
+
clusterCounts[clusterIdx]++;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Update cluster centers (average of assigned embeddings)
|
|
379
|
+
for (let k = 0; k < numClusters; k++) {
|
|
380
|
+
const count = clusterCounts[k];
|
|
381
|
+
const center = clusterCenters[k];
|
|
382
|
+
const sum = clusterSums[k];
|
|
383
|
+
if (count !== undefined && count > 0 && center && sum) {
|
|
384
|
+
for (let i = 0; i < center.length; i++) {
|
|
385
|
+
const sumVal = sum[i];
|
|
386
|
+
if (sumVal !== undefined) {
|
|
387
|
+
center[i] = sumVal / count;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Calculate the total loss function
|
|
396
|
+
* @param graph - The input graph
|
|
397
|
+
* @param embeddings - Map of node IDs to embedding vectors
|
|
398
|
+
* @param clusters - Map of node IDs to cluster assignments
|
|
399
|
+
* @param clusterCenters - Array of cluster center vectors
|
|
400
|
+
* @param lambda - Regularization parameter
|
|
401
|
+
* @returns The total loss value
|
|
402
|
+
*/
|
|
403
|
+
function calculateLoss(
|
|
404
|
+
graph: Graph,
|
|
405
|
+
embeddings: Map<NodeId, number[]>,
|
|
406
|
+
clusters: Map<NodeId, number>,
|
|
407
|
+
clusterCenters: number[][],
|
|
408
|
+
lambda: number,
|
|
409
|
+
): number {
|
|
410
|
+
let clusteringLoss = 0;
|
|
411
|
+
let reconstructionLoss = 0;
|
|
412
|
+
let regularizationLoss = 0;
|
|
413
|
+
|
|
414
|
+
// Clustering loss (distance to cluster centers)
|
|
415
|
+
for (const [nodeId, clusterIdx] of clusters) {
|
|
416
|
+
const embedding = embeddings.get(nodeId);
|
|
417
|
+
if (!embedding) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const center = clusterCenters[clusterIdx];
|
|
422
|
+
if (!center) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
clusteringLoss += euclideanDistance(embedding, center) ** 2;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Graph reconstruction loss
|
|
430
|
+
for (const node of graph.nodes()) {
|
|
431
|
+
const nodeId = node.id;
|
|
432
|
+
const nodeEmbedding = embeddings.get(nodeId);
|
|
433
|
+
if (!nodeEmbedding) {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
for (const neighborId of graph.neighbors(nodeId)) {
|
|
438
|
+
const neighborEmbedding = embeddings.get(neighborId);
|
|
439
|
+
if (!neighborEmbedding) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const distance = euclideanDistance(nodeEmbedding, neighborEmbedding);
|
|
444
|
+
reconstructionLoss += distance ** 2;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Regularization loss
|
|
449
|
+
for (const embedding of embeddings.values()) {
|
|
450
|
+
for (const value of embedding) {
|
|
451
|
+
regularizationLoss += value ** 2;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return clusteringLoss + lambda * reconstructionLoss + lambda * regularizationLoss;
|
|
456
|
+
}
|