@graphty/algorithms 1.3.1 → 1.4.2
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 +358 -413
- package/dist/algorithms.js +564 -261
- package/dist/algorithms.js.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.d.ts +10 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +27 -6
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.d.ts +11 -2
- package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +17 -3
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.d.ts +7 -0
- package/dist/src/algorithms/centrality/degree.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +9 -6
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +21 -3
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js +25 -7
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts +28 -2
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank.js +30 -4
- package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.d.ts +10 -3
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.js +11 -4
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -1
- package/dist/src/algorithms/centrality/hits.d.ts +10 -3
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/hits.js +11 -4
- package/dist/src/algorithms/centrality/hits.js.map +1 -1
- package/dist/src/algorithms/centrality/index.d.ts +1 -1
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +1 -1
- package/dist/src/algorithms/centrality/index.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +10 -3
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/katz.js +13 -8
- package/dist/src/algorithms/centrality/katz.js.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.d.ts +14 -0
- package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.js +16 -1
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts +2 -3
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.js +21 -4
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
- package/dist/src/algorithms/community/index.d.ts.map +1 -1
- package/dist/src/algorithms/community/index.js.map +1 -1
- package/dist/src/algorithms/community/label-propagation.d.ts +0 -3
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
- package/dist/src/algorithms/community/label-propagation.js +13 -6
- package/dist/src/algorithms/community/label-propagation.js.map +1 -1
- package/dist/src/algorithms/community/leiden.d.ts +0 -1
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
- package/dist/src/algorithms/community/leiden.js +37 -6
- package/dist/src/algorithms/community/leiden.js.map +1 -1
- package/dist/src/algorithms/community/louvain-optimized.d.ts +37 -0
- package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -1
- package/dist/src/algorithms/community/louvain-optimized.js +42 -7
- package/dist/src/algorithms/community/louvain-optimized.js.map +1 -1
- package/dist/src/algorithms/community/louvain.d.ts +2 -3
- package/dist/src/algorithms/community/louvain.d.ts.map +1 -1
- package/dist/src/algorithms/community/louvain.js +19 -6
- package/dist/src/algorithms/community/louvain.js.map +1 -1
- package/dist/src/algorithms/community/modularity-utils.d.ts +0 -4
- package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -1
- package/dist/src/algorithms/community/modularity-utils.js +1 -5
- package/dist/src/algorithms/community/modularity-utils.js.map +1 -1
- package/dist/src/algorithms/components/connected.d.ts +23 -0
- package/dist/src/algorithms/components/connected.d.ts.map +1 -1
- package/dist/src/algorithms/components/connected.js +29 -3
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/matching/bipartite.d.ts +10 -0
- package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -1
- package/dist/src/algorithms/matching/bipartite.js +10 -0
- package/dist/src/algorithms/matching/bipartite.js.map +1 -1
- package/dist/src/algorithms/matching/index.d.ts.map +1 -1
- package/dist/src/algorithms/matching/index.js.map +1 -1
- package/dist/src/algorithms/matching/isomorphism.d.ts +8 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -1
- package/dist/src/algorithms/matching/isomorphism.js +36 -2
- package/dist/src/algorithms/matching/isomorphism.js.map +1 -1
- package/dist/src/algorithms/mst/index.d.ts.map +1 -1
- package/dist/src/algorithms/mst/index.js.map +1 -1
- package/dist/src/algorithms/mst/kruskal.d.ts +14 -0
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -1
- package/dist/src/algorithms/mst/kruskal.js +17 -3
- package/dist/src/algorithms/mst/kruskal.js.map +1 -1
- package/dist/src/algorithms/mst/prim.d.ts +9 -0
- package/dist/src/algorithms/mst/prim.d.ts.map +1 -1
- package/dist/src/algorithms/mst/prim.js +9 -0
- package/dist/src/algorithms/mst/prim.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts +10 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.js +10 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +11 -4
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +18 -16
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts +15 -0
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +15 -0
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +17 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +17 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -1
- package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/index.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.d.ts +13 -0
- package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.js +37 -0
- package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.d.ts +23 -0
- package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.js +34 -0
- package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs.d.ts +1 -1
- package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +1 -1
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.d.ts +10 -0
- package/dist/src/algorithms/traversal/dfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +48 -0
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/algorithms/traversal/index.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/index.js.map +1 -1
- package/dist/src/benchmark-all-algorithms.d.ts +2 -0
- package/dist/src/benchmark-all-algorithms.d.ts.map +1 -1
- package/dist/src/benchmark-all-algorithms.js +32 -3
- package/dist/src/benchmark-all-algorithms.js.map +1 -1
- package/dist/src/clustering/hierarchical.d.ts +8 -1
- package/dist/src/clustering/hierarchical.d.ts.map +1 -1
- package/dist/src/clustering/hierarchical.js +22 -6
- package/dist/src/clustering/hierarchical.js.map +1 -1
- package/dist/src/clustering/index.d.ts.map +1 -1
- package/dist/src/clustering/index.js.map +1 -1
- package/dist/src/clustering/k-core.d.ts +2 -5
- package/dist/src/clustering/k-core.d.ts.map +1 -1
- package/dist/src/clustering/k-core.js +14 -12
- package/dist/src/clustering/k-core.js.map +1 -1
- package/dist/src/clustering/mcl.d.ts +6 -0
- package/dist/src/clustering/mcl.d.ts.map +1 -1
- package/dist/src/clustering/mcl.js +34 -3
- package/dist/src/clustering/mcl.js.map +1 -1
- package/dist/src/clustering/spectral.d.ts +4 -1
- package/dist/src/clustering/spectral.d.ts.map +1 -1
- package/dist/src/clustering/spectral.js +61 -28
- package/dist/src/clustering/spectral.js.map +1 -1
- package/dist/src/core/graph.d.ts +45 -0
- package/dist/src/core/graph.d.ts.map +1 -1
- package/dist/src/core/graph.js +45 -1
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/data-structures/index.d.ts.map +1 -1
- package/dist/src/data-structures/index.js.map +1 -1
- package/dist/src/data-structures/priority-queue.d.ts +18 -1
- package/dist/src/data-structures/priority-queue.d.ts.map +1 -1
- package/dist/src/data-structures/priority-queue.js +26 -5
- package/dist/src/data-structures/priority-queue.js.map +1 -1
- package/dist/src/data-structures/union-find.d.ts +21 -0
- package/dist/src/data-structures/union-find.d.ts.map +1 -1
- package/dist/src/data-structures/union-find.js +21 -0
- package/dist/src/data-structures/union-find.js.map +1 -1
- package/dist/src/flow/ford-fulkerson.d.ts +4 -2
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
- package/dist/src/flow/ford-fulkerson.js +31 -2
- package/dist/src/flow/ford-fulkerson.js.map +1 -1
- package/dist/src/flow/min-cut.d.ts +0 -3
- package/dist/src/flow/min-cut.d.ts.map +1 -1
- package/dist/src/flow/min-cut.js +17 -4
- package/dist/src/flow/min-cut.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- 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/link-prediction/adamic-adar.d.ts +26 -0
- package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -1
- package/dist/src/link-prediction/adamic-adar.js +36 -10
- package/dist/src/link-prediction/adamic-adar.js.map +1 -1
- package/dist/src/link-prediction/common-neighbors.d.ts +21 -0
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
- package/dist/src/link-prediction/common-neighbors.js +27 -6
- package/dist/src/link-prediction/common-neighbors.js.map +1 -1
- package/dist/src/link-prediction/index.d.ts.map +1 -1
- package/dist/src/optimized/bit-packed.d.ts +103 -53
- package/dist/src/optimized/bit-packed.d.ts.map +1 -1
- package/dist/src/optimized/bit-packed.js +104 -54
- package/dist/src/optimized/bit-packed.js.map +1 -1
- package/dist/src/optimized/csr-graph.d.ts +87 -24
- package/dist/src/optimized/csr-graph.d.ts.map +1 -1
- package/dist/src/optimized/csr-graph.js +87 -24
- package/dist/src/optimized/csr-graph.js.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.d.ts +37 -16
- package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.js +39 -20
- package/dist/src/optimized/direction-optimized-bfs.js.map +1 -1
- package/dist/src/optimized/graph-adapter.d.ts +58 -9
- package/dist/src/optimized/graph-adapter.d.ts.map +1 -1
- package/dist/src/optimized/graph-adapter.js +58 -10
- package/dist/src/optimized/graph-adapter.js.map +1 -1
- package/dist/src/optimized/index.d.ts +1 -1
- package/dist/src/optimized/index.d.ts.map +1 -1
- package/dist/src/optimized/index.js +3 -1
- package/dist/src/optimized/index.js.map +1 -1
- package/dist/src/pathfinding/astar.d.ts +25 -9
- package/dist/src/pathfinding/astar.d.ts.map +1 -1
- package/dist/src/pathfinding/astar.js +25 -10
- package/dist/src/pathfinding/astar.js.map +1 -1
- package/dist/src/pathfinding/utils.d.ts +15 -6
- package/dist/src/pathfinding/utils.d.ts.map +1 -1
- package/dist/src/pathfinding/utils.js +19 -7
- package/dist/src/pathfinding/utils.js.map +1 -1
- package/dist/src/research/grsbm.d.ts +0 -1
- package/dist/src/research/grsbm.d.ts.map +1 -1
- package/dist/src/research/grsbm.js +22 -9
- package/dist/src/research/grsbm.js.map +1 -1
- package/dist/src/research/index.d.ts.map +1 -1
- package/dist/src/research/index.js.map +1 -1
- package/dist/src/research/sync.d.ts +0 -1
- package/dist/src/research/sync.d.ts.map +1 -1
- package/dist/src/research/sync.js +28 -7
- package/dist/src/research/sync.js.map +1 -1
- package/dist/src/research/terahac.d.ts +0 -1
- package/dist/src/research/terahac.d.ts.map +1 -1
- package/dist/src/research/terahac.js +32 -4
- package/dist/src/research/terahac.js.map +1 -1
- package/dist/src/utils/graph-converters.d.ts +31 -0
- package/dist/src/utils/graph-converters.d.ts.map +1 -1
- package/dist/src/utils/graph-converters.js +31 -0
- package/dist/src/utils/graph-converters.js.map +1 -1
- package/dist/src/utils/graph-utilities.d.ts.map +1 -1
- package/dist/src/utils/graph-utilities.js.map +1 -1
- package/dist/src/utils/math-utilities.d.ts +11 -0
- package/dist/src/utils/math-utilities.d.ts.map +1 -1
- package/dist/src/utils/math-utilities.js +14 -5
- package/dist/src/utils/math-utilities.js.map +1 -1
- package/dist/src/utils/optimization-helpers.d.ts +9 -0
- package/dist/src/utils/optimization-helpers.d.ts.map +1 -1
- package/dist/src/utils/optimization-helpers.js +12 -0
- package/dist/src/utils/optimization-helpers.js.map +1 -1
- package/dist/src/utils/priorityQueue.d.ts +24 -0
- package/dist/src/utils/priorityQueue.d.ts.map +1 -1
- package/dist/src/utils/priorityQueue.js +34 -4
- package/dist/src/utils/priorityQueue.js.map +1 -1
- package/package.json +49 -45
- package/src/algorithms/centrality/betweenness.ts +34 -17
- package/src/algorithms/centrality/closeness.ts +34 -17
- package/src/algorithms/centrality/degree.ts +15 -19
- package/src/algorithms/centrality/delta-pagerank-simple.ts +36 -28
- package/src/algorithms/centrality/delta-pagerank.ts +34 -11
- package/src/algorithms/centrality/eigenvector.ts +14 -15
- package/src/algorithms/centrality/hits.ts +17 -17
- package/src/algorithms/centrality/index.ts +20 -15
- package/src/algorithms/centrality/katz.ts +17 -25
- package/src/algorithms/centrality/pagerank.ts +26 -17
- package/src/algorithms/community/girvan-newman.ts +39 -28
- package/src/algorithms/community/index.ts +6 -7
- package/src/algorithms/community/label-propagation.ts +20 -28
- package/src/algorithms/community/leiden.ts +61 -37
- package/src/algorithms/community/louvain-optimized.ts +49 -20
- package/src/algorithms/community/louvain.ts +35 -25
- package/src/algorithms/community/modularity-utils.ts +5 -17
- package/src/algorithms/components/connected.ts +40 -17
- package/src/algorithms/matching/bipartite.ts +17 -10
- package/src/algorithms/matching/index.ts +4 -4
- package/src/algorithms/matching/isomorphism.ts +53 -23
- package/src/algorithms/mst/index.ts +3 -4
- package/src/algorithms/mst/kruskal.ts +21 -6
- package/src/algorithms/mst/prim.ts +13 -4
- package/src/algorithms/shortest-path/bellman-ford.ts +16 -15
- package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +28 -28
- package/src/algorithms/shortest-path/dijkstra.ts +23 -17
- package/src/algorithms/shortest-path/floyd-warshall.ts +21 -4
- package/src/algorithms/shortest-path/index.ts +5 -5
- package/src/algorithms/traversal/bfs-unified.ts +64 -60
- package/src/algorithms/traversal/bfs-variants.ts +61 -37
- package/src/algorithms/traversal/bfs.ts +1 -7
- package/src/algorithms/traversal/dfs.ts +61 -42
- package/src/algorithms/traversal/index.ts +3 -3
- package/src/benchmark-all-algorithms.ts +57 -25
- package/src/clustering/hierarchical.ts +40 -34
- package/src/clustering/index.ts +8 -8
- package/src/clustering/k-core.ts +26 -42
- package/src/clustering/mcl.ts +53 -23
- package/src/clustering/spectral.ts +91 -57
- package/src/core/graph.ts +54 -14
- package/src/data-structures/index.ts +2 -2
- package/src/data-structures/priority-queue.ts +36 -11
- package/src/data-structures/union-find.ts +22 -1
- package/src/flow/ford-fulkerson.ts +62 -35
- package/src/flow/min-cut.ts +43 -45
- package/src/index.ts +3 -2
- package/src/link-prediction/adamic-adar.ts +65 -51
- package/src/link-prediction/common-neighbors.ts +45 -36
- package/src/link-prediction/index.ts +1 -1
- package/src/optimized/bit-packed.ts +106 -56
- package/src/optimized/csr-graph.ts +93 -30
- package/src/optimized/direction-optimized-bfs.ts +44 -25
- package/src/optimized/graph-adapter.ts +64 -16
- package/src/optimized/index.ts +7 -9
- package/src/pathfinding/astar.ts +40 -25
- package/src/pathfinding/utils.ts +20 -13
- package/src/research/grsbm.ts +30 -24
- package/src/research/index.ts +4 -4
- package/src/research/sync.ts +39 -23
- package/src/research/terahac.ts +54 -20
- package/src/utils/graph-converters.ts +42 -14
- package/src/utils/graph-utilities.ts +11 -37
- package/src/utils/math-utilities.ts +17 -8
- package/src/utils/optimization-helpers.ts +27 -21
- package/src/utils/priorityQueue.ts +40 -6
package/src/research/grsbm.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {Graph} from "../core/graph.js";
|
|
2
|
-
import type {NodeId} from "../types/index.js";
|
|
3
|
-
import {SeededRandom} from "../utils/math-utilities.js";
|
|
1
|
+
import type { Graph } from "../core/graph.js";
|
|
2
|
+
import type { NodeId } from "../types/index.js";
|
|
3
|
+
import { SeededRandom } from "../utils/math-utilities.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Configuration options for the GRSBM (Greedy Recursive Spectral Bisection) algorithm
|
|
@@ -80,7 +80,6 @@ export interface ClusterExplanation {
|
|
|
80
80
|
* community structure by tracking the reasoning behind each split.
|
|
81
81
|
*
|
|
82
82
|
* Based on: "Explainable Community Detection via Hierarchical Spectral Clustering" (2024)
|
|
83
|
-
*
|
|
84
83
|
* @param graph - Input graph to cluster
|
|
85
84
|
* @param config - Configuration options
|
|
86
85
|
* @returns Hierarchical clustering result with explanations
|
|
@@ -109,7 +108,7 @@ export function grsbm(graph: Graph, config: GRSBMConfig = {}): GRSBMResult {
|
|
|
109
108
|
|
|
110
109
|
// Initialize root cluster with all nodes
|
|
111
110
|
const rootMembers = new Set(nodes.map((node) => node.id));
|
|
112
|
-
const initialModularity = calculateModularity(graph, new Map([...
|
|
111
|
+
const initialModularity = calculateModularity(graph, new Map([...rootMembers].map((id) => [id, 0])));
|
|
113
112
|
|
|
114
113
|
const root: GRSBMCluster = {
|
|
115
114
|
id: "root",
|
|
@@ -133,25 +132,18 @@ export function grsbm(graph: Graph, config: GRSBMConfig = {}): GRSBMResult {
|
|
|
133
132
|
}
|
|
134
133
|
|
|
135
134
|
// Check stopping criteria
|
|
136
|
-
if (currentCluster.depth >= maxDepth ||
|
|
137
|
-
currentCluster.members.size < minClusterSize * 2) {
|
|
135
|
+
if (currentCluster.depth >= maxDepth || currentCluster.members.size < minClusterSize * 2) {
|
|
138
136
|
continue;
|
|
139
137
|
}
|
|
140
138
|
|
|
141
139
|
// Attempt spectral bisection
|
|
142
|
-
const bisectionResult = spectralBisection(
|
|
143
|
-
graph,
|
|
144
|
-
currentCluster,
|
|
145
|
-
numEigenvectors,
|
|
146
|
-
tolerance,
|
|
147
|
-
maxIterations,
|
|
148
|
-
);
|
|
140
|
+
const bisectionResult = spectralBisection(graph, currentCluster, numEigenvectors, tolerance, maxIterations);
|
|
149
141
|
|
|
150
142
|
if (!bisectionResult) {
|
|
151
143
|
continue; // Could not split this cluster
|
|
152
144
|
}
|
|
153
145
|
|
|
154
|
-
const {leftMembers, rightMembers, spectralScore, keyNodes, spectralValues, reason} = bisectionResult;
|
|
146
|
+
const { leftMembers, rightMembers, spectralScore, keyNodes, spectralValues, reason } = bisectionResult;
|
|
155
147
|
|
|
156
148
|
// Create child clusters
|
|
157
149
|
const leftCluster: GRSBMCluster = {
|
|
@@ -183,7 +175,8 @@ export function grsbm(graph: Graph, config: GRSBMConfig = {}): GRSBMResult {
|
|
|
183
175
|
const modularityImprovement = newModularity - currentCluster.modularity;
|
|
184
176
|
|
|
185
177
|
// Only split if modularity improves or is neutral (allow small splits)
|
|
186
|
-
if (modularityImprovement >= -0.01) {
|
|
178
|
+
if (modularityImprovement >= -0.01) {
|
|
179
|
+
// Small tolerance for neutral splits
|
|
187
180
|
leftCluster.modularity = newModularity;
|
|
188
181
|
rightCluster.modularity = newModularity;
|
|
189
182
|
|
|
@@ -245,6 +238,12 @@ export function grsbm(graph: Graph, config: GRSBMConfig = {}): GRSBMResult {
|
|
|
245
238
|
|
|
246
239
|
/**
|
|
247
240
|
* Perform spectral bisection on a cluster
|
|
241
|
+
* @param graph - The input graph
|
|
242
|
+
* @param cluster - The cluster to bisect
|
|
243
|
+
* @param numEigenvectors - Number of eigenvectors to compute
|
|
244
|
+
* @param tolerance - Convergence tolerance
|
|
245
|
+
* @param maxIterations - Maximum number of iterations
|
|
246
|
+
* @returns Bisection result with left/right members, scores, and explanation, or null if bisection failed
|
|
248
247
|
*/
|
|
249
248
|
function spectralBisection(
|
|
250
249
|
graph: Graph,
|
|
@@ -278,9 +277,7 @@ function spectralBisection(
|
|
|
278
277
|
}
|
|
279
278
|
|
|
280
279
|
// Find optimal split point
|
|
281
|
-
const sortedIndices = eigenvector
|
|
282
|
-
.map((value, index) => ({value, index}))
|
|
283
|
-
.sort((a, b) => a.value - b.value);
|
|
280
|
+
const sortedIndices = eigenvector.map((value, index) => ({ value, index })).sort((a, b) => a.value - b.value);
|
|
284
281
|
|
|
285
282
|
let bestSplitIndex = Math.floor(n / 2);
|
|
286
283
|
let bestModularity = -Infinity;
|
|
@@ -335,7 +332,7 @@ function spectralBisection(
|
|
|
335
332
|
|
|
336
333
|
// Identify key nodes (those with extreme spectral values)
|
|
337
334
|
const extremeThreshold = 0.1;
|
|
338
|
-
const sortedValues = [...
|
|
335
|
+
const sortedValues = [...eigenvector].sort((a, b) => Math.abs(b) - Math.abs(a));
|
|
339
336
|
const threshold = sortedValues[Math.floor(sortedValues.length * extremeThreshold)] ?? 0;
|
|
340
337
|
|
|
341
338
|
const keyNodes: NodeId[] = [];
|
|
@@ -369,6 +366,9 @@ function spectralBisection(
|
|
|
369
366
|
|
|
370
367
|
/**
|
|
371
368
|
* Create Laplacian matrix for a subgraph
|
|
369
|
+
* @param graph - The input graph
|
|
370
|
+
* @param nodes - The nodes to include in the subgraph
|
|
371
|
+
* @returns The Laplacian matrix as a 2D array
|
|
372
372
|
*/
|
|
373
373
|
function createLaplacianMatrix(graph: Graph, nodes: NodeId[]): number[][] {
|
|
374
374
|
const n = nodes.length;
|
|
@@ -383,7 +383,7 @@ function createLaplacianMatrix(graph: Graph, nodes: NodeId[]): number[][] {
|
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
// Initialize Laplacian matrix
|
|
386
|
-
const laplacian = Array.from({length: n}, () => new Array<number>(n).fill(0));
|
|
386
|
+
const laplacian = Array.from({ length: n }, () => new Array<number>(n).fill(0));
|
|
387
387
|
|
|
388
388
|
// Fill adjacency part and calculate degrees
|
|
389
389
|
const degrees = new Array<number>(n).fill(0);
|
|
@@ -420,6 +420,10 @@ function createLaplacianMatrix(graph: Graph, nodes: NodeId[]): number[][] {
|
|
|
420
420
|
|
|
421
421
|
/**
|
|
422
422
|
* Compute the Fiedler vector (second smallest eigenvector) using power iteration
|
|
423
|
+
* @param laplacian - The Laplacian matrix
|
|
424
|
+
* @param tolerance - Convergence tolerance
|
|
425
|
+
* @param maxIterations - Maximum number of iterations
|
|
426
|
+
* @returns The Fiedler vector, or null if computation failed
|
|
423
427
|
*/
|
|
424
428
|
function computeFiedlerVector(laplacian: number[][], tolerance: number, maxIterations: number): number[] | null {
|
|
425
429
|
const n = laplacian.length;
|
|
@@ -443,7 +447,7 @@ function computeFiedlerVector(laplacian: number[][], tolerance: number, maxItera
|
|
|
443
447
|
}
|
|
444
448
|
|
|
445
449
|
// Normalize
|
|
446
|
-
const norm = Math.sqrt(vector.reduce((sum: number, val: number) => sum +
|
|
450
|
+
const norm = Math.sqrt(vector.reduce((sum: number, val: number) => sum + val * val, 0));
|
|
447
451
|
if (norm > 0) {
|
|
448
452
|
for (let i = 0; i < n; i++) {
|
|
449
453
|
const val = vector[i];
|
|
@@ -494,7 +498,7 @@ function computeFiedlerVector(laplacian: number[][], tolerance: number, maxItera
|
|
|
494
498
|
}
|
|
495
499
|
|
|
496
500
|
// Normalize
|
|
497
|
-
const newNorm = Math.sqrt(newVector.reduce((sum, val) => sum +
|
|
501
|
+
const newNorm = Math.sqrt(newVector.reduce((sum, val) => sum + val * val, 0));
|
|
498
502
|
if (newNorm < tolerance) {
|
|
499
503
|
break; // Converged to zero vector
|
|
500
504
|
}
|
|
@@ -528,6 +532,9 @@ function computeFiedlerVector(laplacian: number[][], tolerance: number, maxItera
|
|
|
528
532
|
|
|
529
533
|
/**
|
|
530
534
|
* Calculate modularity of a graph partitioning
|
|
535
|
+
* @param graph - The input graph
|
|
536
|
+
* @param assignment - Map from node ID to community ID
|
|
537
|
+
* @returns The modularity score
|
|
531
538
|
*/
|
|
532
539
|
function calculateModularity(graph: Graph, assignment: Map<NodeId, number>): number {
|
|
533
540
|
const totalEdges = graph.uniqueEdgeCount;
|
|
@@ -577,4 +584,3 @@ function calculateModularity(graph: Graph, assignment: Map<NodeId, number>): num
|
|
|
577
584
|
|
|
578
585
|
return modularity;
|
|
579
586
|
}
|
|
580
|
-
|
package/src/research/index.ts
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
// Synergistic Deep Graph Clustering
|
|
10
|
-
export {syncClustering, type SynCConfig, type SynCResult} from "./sync.js";
|
|
10
|
+
export { syncClustering, type SynCConfig, type SynCResult } from "./sync.js";
|
|
11
11
|
|
|
12
12
|
// TeraHAC - Hierarchical Agglomerative Clustering
|
|
13
|
-
export {teraHAC, type TeraHACConfig, type TeraHACResult} from "./terahac.js";
|
|
14
|
-
export {type ClusterNode as TeraHACClusterNode} from "./terahac.js";
|
|
13
|
+
export { teraHAC, type TeraHACConfig, type TeraHACResult } from "./terahac.js";
|
|
14
|
+
export { type ClusterNode as TeraHACClusterNode } from "./terahac.js";
|
|
15
15
|
|
|
16
16
|
// GRSBM - Greedy Recursive Spectral Bisection with Modularity
|
|
17
|
-
export {type ClusterExplanation, grsbm, type GRSBMCluster, type GRSBMConfig, type GRSBMResult} from "./grsbm.js";
|
|
17
|
+
export { type ClusterExplanation, grsbm, type GRSBMCluster, type GRSBMConfig, type GRSBMResult } from "./grsbm.js";
|
package/src/research/sync.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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";
|
|
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
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Configuration options for the SynC (Synergistic Deep Graph Clustering) algorithm
|
|
@@ -44,20 +44,12 @@ export interface SynCResult {
|
|
|
44
44
|
* embeddings and cluster assignments while preserving graph structure.
|
|
45
45
|
*
|
|
46
46
|
* Based on: "Synergistic Deep Graph Clustering" (arXiv:2406.15797, June 2024)
|
|
47
|
-
*
|
|
48
47
|
* @param graph - Input graph to cluster
|
|
49
48
|
* @param config - Configuration options
|
|
50
49
|
* @returns Clustering result with assignments and embeddings
|
|
51
50
|
*/
|
|
52
51
|
export function syncClustering(graph: Graph, config: SynCConfig): SynCResult {
|
|
53
|
-
const {
|
|
54
|
-
numClusters,
|
|
55
|
-
maxIterations = 100,
|
|
56
|
-
tolerance = 1e-6,
|
|
57
|
-
seed = 42,
|
|
58
|
-
learningRate = 0.01,
|
|
59
|
-
lambda = 0.1,
|
|
60
|
-
} = config;
|
|
52
|
+
const { numClusters, maxIterations = 100, tolerance = 1e-6, seed = 42, learningRate = 0.01, lambda = 0.1 } = config;
|
|
61
53
|
|
|
62
54
|
// Set random seed for reproducibility (save original)
|
|
63
55
|
const originalRandom = Math.random;
|
|
@@ -78,7 +70,9 @@ export function syncClustering(graph: Graph, config: SynCConfig): SynCResult {
|
|
|
78
70
|
}
|
|
79
71
|
|
|
80
72
|
if (numClusters <= 0 || numClusters > nodeCount) {
|
|
81
|
-
throw new Error(
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Invalid number of clusters: ${String(numClusters)}. Must be between 1 and ${String(nodeCount)}`,
|
|
75
|
+
);
|
|
82
76
|
}
|
|
83
77
|
|
|
84
78
|
// Initialize node embeddings (simplified version using graph features)
|
|
@@ -184,6 +178,10 @@ export function syncClustering(graph: Graph, config: SynCConfig): SynCResult {
|
|
|
184
178
|
|
|
185
179
|
/**
|
|
186
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
|
|
187
185
|
*/
|
|
188
186
|
function initializeNodeEmbedding(graph: Graph, nodeId: NodeId, dim: number): number[] {
|
|
189
187
|
const embedding = new Array<number>(dim).fill(0);
|
|
@@ -194,7 +192,7 @@ function initializeNodeEmbedding(graph: Graph, nodeId: NodeId, dim: number): num
|
|
|
194
192
|
|
|
195
193
|
// Initialize with small random values influenced by graph structure
|
|
196
194
|
for (let i = 0; i < dim; i++) {
|
|
197
|
-
embedding[i] = (
|
|
195
|
+
embedding[i] = (Math.random() - 0.5) * 0.1 + normalizedDegree * 0.1;
|
|
198
196
|
}
|
|
199
197
|
|
|
200
198
|
return embedding;
|
|
@@ -202,6 +200,9 @@ function initializeNodeEmbedding(graph: Graph, nodeId: NodeId, dim: number): num
|
|
|
202
200
|
|
|
203
201
|
/**
|
|
204
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
|
|
205
206
|
*/
|
|
206
207
|
function initializeClusterCenters(embeddings: Map<NodeId, number[]>, numClusters: number): number[][] {
|
|
207
208
|
const embeddingArray = Array.from(embeddings.values());
|
|
@@ -210,7 +211,7 @@ function initializeClusterCenters(embeddings: Map<NodeId, number[]>, numClusters
|
|
|
210
211
|
// Choose first center randomly
|
|
211
212
|
const firstCenter = embeddingArray[Math.floor(Math.random() * embeddingArray.length)];
|
|
212
213
|
if (firstCenter) {
|
|
213
|
-
centers.push([...
|
|
214
|
+
centers.push([...firstCenter] as number[]);
|
|
214
215
|
}
|
|
215
216
|
|
|
216
217
|
// Choose remaining centers using k-means++ initialization
|
|
@@ -239,7 +240,7 @@ function initializeClusterCenters(embeddings: Map<NodeId, number[]>, numClusters
|
|
|
239
240
|
if (randomValue <= 0) {
|
|
240
241
|
const newCenter = embeddingArray[i];
|
|
241
242
|
if (newCenter) {
|
|
242
|
-
centers.push([...
|
|
243
|
+
centers.push([...newCenter] as number[]);
|
|
243
244
|
}
|
|
244
245
|
|
|
245
246
|
break;
|
|
@@ -252,6 +253,11 @@ function initializeClusterCenters(embeddings: Map<NodeId, number[]>, numClusters
|
|
|
252
253
|
|
|
253
254
|
/**
|
|
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
|
|
259
|
+
* @param learningRate - Learning rate for gradient descent
|
|
260
|
+
* @param lambda - Regularization parameter
|
|
255
261
|
*/
|
|
256
262
|
function updateEmbeddings(
|
|
257
263
|
graph: Graph,
|
|
@@ -293,7 +299,7 @@ function updateEmbeddings(
|
|
|
293
299
|
const gradVal = gradient[i];
|
|
294
300
|
const diffVal = diff[i];
|
|
295
301
|
if (gradVal !== undefined && diffVal !== undefined) {
|
|
296
|
-
gradient[i] = gradVal +
|
|
302
|
+
gradient[i] = gradVal + lambda * diffVal;
|
|
297
303
|
}
|
|
298
304
|
}
|
|
299
305
|
}
|
|
@@ -303,7 +309,7 @@ function updateEmbeddings(
|
|
|
303
309
|
const gradVal = gradient[i];
|
|
304
310
|
const nodeVal = nodeEmbedding[i];
|
|
305
311
|
if (gradVal !== undefined && nodeVal !== undefined) {
|
|
306
|
-
gradient[i] = gradVal +
|
|
312
|
+
gradient[i] = gradVal + lambda * nodeVal;
|
|
307
313
|
}
|
|
308
314
|
}
|
|
309
315
|
}
|
|
@@ -319,7 +325,7 @@ function updateEmbeddings(
|
|
|
319
325
|
const embVal = embedding[i];
|
|
320
326
|
const gradVal = gradient[i];
|
|
321
327
|
if (embVal !== undefined && gradVal !== undefined) {
|
|
322
|
-
embedding[i] = embVal -
|
|
328
|
+
embedding[i] = embVal - learningRate * gradVal;
|
|
323
329
|
}
|
|
324
330
|
}
|
|
325
331
|
}
|
|
@@ -327,6 +333,10 @@ function updateEmbeddings(
|
|
|
327
333
|
|
|
328
334
|
/**
|
|
329
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
|
|
330
340
|
*/
|
|
331
341
|
function updateClusterCenters(
|
|
332
342
|
embeddings: Map<NodeId, number[]>,
|
|
@@ -335,8 +345,9 @@ function updateClusterCenters(
|
|
|
335
345
|
numClusters: number,
|
|
336
346
|
): void {
|
|
337
347
|
const dimensions = clusterCenters[0]?.length ?? 0;
|
|
338
|
-
const clusterSums: number[][] = Array.from(
|
|
339
|
-
|
|
348
|
+
const clusterSums: number[][] = Array.from(
|
|
349
|
+
{ length: numClusters },
|
|
350
|
+
() => new Array(dimensions).fill(0) as number[],
|
|
340
351
|
);
|
|
341
352
|
const clusterCounts = new Array<number>(numClusters).fill(0);
|
|
342
353
|
|
|
@@ -382,6 +393,12 @@ function updateClusterCenters(
|
|
|
382
393
|
|
|
383
394
|
/**
|
|
384
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
|
|
385
402
|
*/
|
|
386
403
|
function calculateLoss(
|
|
387
404
|
graph: Graph,
|
|
@@ -435,6 +452,5 @@ function calculateLoss(
|
|
|
435
452
|
}
|
|
436
453
|
}
|
|
437
454
|
|
|
438
|
-
return clusteringLoss +
|
|
455
|
+
return clusteringLoss + lambda * reconstructionLoss + lambda * regularizationLoss;
|
|
439
456
|
}
|
|
440
|
-
|
package/src/research/terahac.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {Graph} from "../core/graph.js";
|
|
2
|
-
import type {NodeId} from "../types/index.js";
|
|
1
|
+
import type { Graph } from "../core/graph.js";
|
|
2
|
+
import type { NodeId } from "../types/index.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Configuration options for the TeraHAC (Hierarchical Agglomerative Clustering) algorithm
|
|
@@ -60,7 +60,6 @@ export interface TeraHACResult {
|
|
|
60
60
|
*
|
|
61
61
|
* Based on: "Scaling Hierarchical Agglomerative Clustering to Trillion-Edge Graphs"
|
|
62
62
|
* Google Research 2024
|
|
63
|
-
*
|
|
64
63
|
* @param graph - Input graph to cluster
|
|
65
64
|
* @param config - Configuration options
|
|
66
65
|
* @returns Hierarchical clustering result
|
|
@@ -88,7 +87,9 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
|
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
if (nodeCount > maxNodes) {
|
|
91
|
-
onWarning(
|
|
90
|
+
onWarning(
|
|
91
|
+
`Graph has ${String(nodeCount)} nodes, which exceeds maxNodes (${String(maxNodes)}). Performance may be degraded.`,
|
|
92
|
+
);
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
// Initialize each node as its own cluster
|
|
@@ -122,7 +123,7 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
|
|
|
122
123
|
// Perform agglomerative clustering
|
|
123
124
|
while (clusters.size > 1 && mergeCandidates.length > 0) {
|
|
124
125
|
// Find closest pair of clusters
|
|
125
|
-
const {cluster1Id, cluster2Id, distance} = findClosestPair(mergeCandidates);
|
|
126
|
+
const { cluster1Id, cluster2Id, distance } = findClosestPair(mergeCandidates);
|
|
126
127
|
|
|
127
128
|
// Check stopping criteria
|
|
128
129
|
if (numClusters && clusters.size <= numClusters) {
|
|
@@ -142,7 +143,7 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
|
|
|
142
143
|
|
|
143
144
|
// Create new merged cluster
|
|
144
145
|
const newClusterId = (nextClusterId++).toString();
|
|
145
|
-
const mergedMembers = new Set([...
|
|
146
|
+
const mergedMembers = new Set([...cluster1.members, ...cluster2.members]);
|
|
146
147
|
|
|
147
148
|
const newCluster: ClusterNode = {
|
|
148
149
|
id: newClusterId,
|
|
@@ -182,7 +183,7 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
|
|
|
182
183
|
|
|
183
184
|
const newRoot: ClusterNode = {
|
|
184
185
|
id: (nextClusterId++).toString(),
|
|
185
|
-
members: new Set([...
|
|
186
|
+
members: new Set([...root.members, ...currentCluster.members]),
|
|
186
187
|
left: root,
|
|
187
188
|
right: currentCluster,
|
|
188
189
|
distance: Infinity,
|
|
@@ -216,10 +217,14 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
|
|
|
216
217
|
|
|
217
218
|
/**
|
|
218
219
|
* Calculate distance matrix between all pairs of nodes
|
|
220
|
+
* @param graph - The input graph
|
|
221
|
+
* @param nodes - Array of nodes to calculate distances between
|
|
222
|
+
* @param useGraphDistance - Whether to use graph-based shortest path distances
|
|
223
|
+
* @returns 2D distance matrix
|
|
219
224
|
*/
|
|
220
|
-
function calculateDistanceMatrix(graph: Graph, nodes: {id: NodeId}[], useGraphDistance: boolean): number[][] {
|
|
225
|
+
function calculateDistanceMatrix(graph: Graph, nodes: { id: NodeId }[], useGraphDistance: boolean): number[][] {
|
|
221
226
|
const n = nodes.length;
|
|
222
|
-
const matrix: number[][] = Array.from({length: n}, () => new Array(n).fill(Infinity) as number[]);
|
|
227
|
+
const matrix: number[][] = Array.from({ length: n }, () => new Array(n).fill(Infinity) as number[]);
|
|
223
228
|
|
|
224
229
|
if (useGraphDistance) {
|
|
225
230
|
// Use graph-based distances (shortest path)
|
|
@@ -286,6 +291,9 @@ function calculateDistanceMatrix(graph: Graph, nodes: {id: NodeId}[], useGraphDi
|
|
|
286
291
|
|
|
287
292
|
/**
|
|
288
293
|
* BFS-based shortest path calculation from a source node
|
|
294
|
+
* @param graph - The input graph
|
|
295
|
+
* @param source - The source node ID
|
|
296
|
+
* @returns Map of node IDs to their distances from the source
|
|
289
297
|
*/
|
|
290
298
|
function bfsShortestPaths(graph: Graph, source: NodeId): Map<NodeId, number> {
|
|
291
299
|
const distances = new Map<NodeId, number>();
|
|
@@ -317,12 +325,15 @@ function bfsShortestPaths(graph: Graph, source: NodeId): Map<NodeId, number> {
|
|
|
317
325
|
|
|
318
326
|
/**
|
|
319
327
|
* Initialize merge candidates priority queue
|
|
328
|
+
* @param clusters - Map of cluster IDs to cluster nodes
|
|
329
|
+
* @param distanceMatrix - 2D distance matrix between nodes
|
|
330
|
+
* @returns Array of merge candidates sorted by distance
|
|
320
331
|
*/
|
|
321
332
|
function initializeMergeCandidates(
|
|
322
333
|
clusters: Map<string, ClusterNode>,
|
|
323
334
|
distanceMatrix: number[][],
|
|
324
|
-
): {cluster1Id: string
|
|
325
|
-
const candidates: {cluster1Id: string
|
|
335
|
+
): { cluster1Id: string; cluster2Id: string; distance: number }[] {
|
|
336
|
+
const candidates: { cluster1Id: string; cluster2Id: string; distance: number }[] = [];
|
|
326
337
|
const clusterIds = Array.from(clusters.keys());
|
|
327
338
|
|
|
328
339
|
for (let i = 0; i < clusterIds.length; i++) {
|
|
@@ -355,10 +366,14 @@ function initializeMergeCandidates(
|
|
|
355
366
|
|
|
356
367
|
/**
|
|
357
368
|
* Find the closest pair of clusters
|
|
369
|
+
* @param mergeCandidates - Array of merge candidates
|
|
370
|
+
* @returns The closest cluster pair with their distance
|
|
358
371
|
*/
|
|
359
|
-
function findClosestPair(
|
|
360
|
-
|
|
361
|
-
|
|
372
|
+
function findClosestPair(mergeCandidates: { cluster1Id: string; cluster2Id: string; distance: number }[]): {
|
|
373
|
+
cluster1Id: string;
|
|
374
|
+
cluster2Id: string;
|
|
375
|
+
distance: number;
|
|
376
|
+
} {
|
|
362
377
|
// Return the first (closest) valid candidate
|
|
363
378
|
const candidate = mergeCandidates.shift();
|
|
364
379
|
if (!candidate) {
|
|
@@ -370,9 +385,16 @@ function findClosestPair(
|
|
|
370
385
|
|
|
371
386
|
/**
|
|
372
387
|
* Update merge candidates after a merge operation
|
|
388
|
+
* @param mergeCandidates - Array of merge candidates to update
|
|
389
|
+
* @param oldCluster1Id - ID of first merged cluster
|
|
390
|
+
* @param oldCluster2Id - ID of second merged cluster
|
|
391
|
+
* @param newClusterId - ID of the newly created cluster
|
|
392
|
+
* @param clusters - Map of cluster IDs to cluster nodes
|
|
393
|
+
* @param distanceMatrix - 2D distance matrix between nodes
|
|
394
|
+
* @param linkage - Linkage criterion to use
|
|
373
395
|
*/
|
|
374
396
|
function updateMergeCandidates(
|
|
375
|
-
mergeCandidates: {cluster1Id: string
|
|
397
|
+
mergeCandidates: { cluster1Id: string; cluster2Id: string; distance: number }[],
|
|
376
398
|
oldCluster1Id: string,
|
|
377
399
|
oldCluster2Id: string,
|
|
378
400
|
newClusterId: string,
|
|
@@ -387,8 +409,12 @@ function updateMergeCandidates(
|
|
|
387
409
|
continue;
|
|
388
410
|
}
|
|
389
411
|
|
|
390
|
-
if (
|
|
391
|
-
candidate.
|
|
412
|
+
if (
|
|
413
|
+
candidate.cluster1Id === oldCluster1Id ||
|
|
414
|
+
candidate.cluster1Id === oldCluster2Id ||
|
|
415
|
+
candidate.cluster2Id === oldCluster1Id ||
|
|
416
|
+
candidate.cluster2Id === oldCluster2Id
|
|
417
|
+
) {
|
|
392
418
|
mergeCandidates.splice(i, 1);
|
|
393
419
|
}
|
|
394
420
|
}
|
|
@@ -416,6 +442,11 @@ function updateMergeCandidates(
|
|
|
416
442
|
|
|
417
443
|
/**
|
|
418
444
|
* Calculate distance between two clusters based on linkage criterion
|
|
445
|
+
* @param cluster1 - First cluster
|
|
446
|
+
* @param cluster2 - Second cluster
|
|
447
|
+
* @param distanceMatrix - 2D distance matrix between nodes
|
|
448
|
+
* @param linkage - Linkage criterion (single, complete, average, ward)
|
|
449
|
+
* @returns The distance between the two clusters
|
|
419
450
|
*/
|
|
420
451
|
function calculateClusterDistance(
|
|
421
452
|
cluster1: ClusterNode,
|
|
@@ -451,14 +482,14 @@ function calculateClusterDistance(
|
|
|
451
482
|
// Apply linkage criterion
|
|
452
483
|
switch (linkage) {
|
|
453
484
|
case "single":
|
|
454
|
-
return Math.min(...
|
|
485
|
+
return Math.min(...distances);
|
|
455
486
|
case "complete":
|
|
456
|
-
return Math.max(...
|
|
487
|
+
return Math.max(...distances);
|
|
457
488
|
case "average":
|
|
458
489
|
return distances.reduce((sum, d) => sum + d, 0) / distances.length;
|
|
459
490
|
case "ward":
|
|
460
491
|
// Simplified Ward linkage (would need cluster centroids for full implementation)
|
|
461
|
-
return distances.reduce((sum, d) => sum +
|
|
492
|
+
return distances.reduce((sum, d) => sum + d * d, 0) / distances.length;
|
|
462
493
|
default:
|
|
463
494
|
return distances.reduce((sum, d) => sum + d, 0) / distances.length;
|
|
464
495
|
}
|
|
@@ -466,6 +497,9 @@ function calculateClusterDistance(
|
|
|
466
497
|
|
|
467
498
|
/**
|
|
468
499
|
* Extract flat clustering from dendrogram
|
|
500
|
+
* @param dendrogram - The root of the dendrogram tree
|
|
501
|
+
* @param numClusters - The target number of clusters
|
|
502
|
+
* @returns Map of node IDs to cluster assignments
|
|
469
503
|
*/
|
|
470
504
|
function extractFlatClustering(dendrogram: ClusterNode, numClusters: number): Map<NodeId, number> {
|
|
471
505
|
const clusters = new Map<NodeId, number>();
|