@graphty/algorithms 1.3.1 → 1.5.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 +368 -422
- package/dist/algorithms.js +567 -264
- 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 +12 -5
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +19 -17
- 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 +23 -10
- 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 +29 -8
- 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/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +56 -65
- 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 +29 -29
- 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 +31 -25
- package/src/research/index.ts +4 -4
- package/src/research/sync.ts +40 -24
- 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/clustering/mcl.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
|
* Markov Clustering (MCL) algorithm implementation
|
|
@@ -31,11 +31,11 @@ export interface MCLResult {
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Perform Markov Clustering on a graph
|
|
34
|
+
* @param graph - The input graph to cluster
|
|
35
|
+
* @param options - MCL algorithm options (expansion, inflation, iterations, etc.)
|
|
36
|
+
* @returns MCL clustering result with communities, attractors, and convergence info
|
|
34
37
|
*/
|
|
35
|
-
export function markovClustering(
|
|
36
|
-
graph: Graph,
|
|
37
|
-
options: MCLOptions = {},
|
|
38
|
-
): MCLResult {
|
|
38
|
+
export function markovClustering(graph: Graph, options: MCLOptions = {}): MCLResult {
|
|
39
39
|
const {
|
|
40
40
|
expansion = 2,
|
|
41
41
|
inflation = 2,
|
|
@@ -65,7 +65,7 @@ export function markovClustering(
|
|
|
65
65
|
let iteration = 0;
|
|
66
66
|
|
|
67
67
|
for (iteration = 0; iteration < maxIterations; iteration++) {
|
|
68
|
-
const oldMatrix = matrix.map((row) => [...
|
|
68
|
+
const oldMatrix = matrix.map((row) => [...row]);
|
|
69
69
|
|
|
70
70
|
// Expansion step (matrix multiplication)
|
|
71
71
|
matrix = matrixPower(matrix, expansion);
|
|
@@ -84,7 +84,7 @@ export function markovClustering(
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// Extract clusters from final matrix
|
|
87
|
-
const {communities, attractors} = extractClusters(matrix, nodeIds);
|
|
87
|
+
const { communities, attractors } = extractClusters(matrix, nodeIds);
|
|
88
88
|
|
|
89
89
|
return {
|
|
90
90
|
communities,
|
|
@@ -96,10 +96,14 @@ export function markovClustering(
|
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* Build initial transition matrix from graph
|
|
99
|
+
* @param graph - The input graph
|
|
100
|
+
* @param nodeIds - Array of node IDs in the graph
|
|
101
|
+
* @param selfLoops - Whether to add self-loops to the matrix
|
|
102
|
+
* @returns Column-normalized transition matrix
|
|
99
103
|
*/
|
|
100
104
|
function buildTransitionMatrix(graph: Graph, nodeIds: NodeId[], selfLoops: boolean): number[][] {
|
|
101
105
|
const n = nodeIds.length;
|
|
102
|
-
const matrix = Array.from({length: n}, (): number[] => Array(n).fill(0) as number[]);
|
|
106
|
+
const matrix = Array.from({ length: n }, (): number[] => Array(n).fill(0) as number[]);
|
|
103
107
|
const nodeToIndex = new Map<NodeId, number>();
|
|
104
108
|
|
|
105
109
|
nodeIds.forEach((id, index) => nodeToIndex.set(id, index));
|
|
@@ -168,6 +172,9 @@ function buildTransitionMatrix(graph: Graph, nodeIds: NodeId[], selfLoops: boole
|
|
|
168
172
|
|
|
169
173
|
/**
|
|
170
174
|
* Raise matrix to a power (for expansion step)
|
|
175
|
+
* @param matrix - Input square matrix
|
|
176
|
+
* @param power - Power to raise the matrix to
|
|
177
|
+
* @returns Matrix raised to the specified power
|
|
171
178
|
*/
|
|
172
179
|
function matrixPower(matrix: number[][], power: number): number[][] {
|
|
173
180
|
if (power === 1) {
|
|
@@ -187,13 +194,16 @@ function matrixPower(matrix: number[][], power: number): number[][] {
|
|
|
187
194
|
|
|
188
195
|
/**
|
|
189
196
|
* Multiply two matrices
|
|
197
|
+
* @param a - First matrix
|
|
198
|
+
* @param b - Second matrix
|
|
199
|
+
* @returns Product matrix a * b
|
|
190
200
|
*/
|
|
191
201
|
function matrixMultiply(a: number[][], b: number[][]): number[][] {
|
|
192
202
|
const n = a.length;
|
|
193
203
|
const m = b[0]?.length ?? 0;
|
|
194
204
|
const p = b.length;
|
|
195
205
|
|
|
196
|
-
const result: number[][] = Array.from({length: n}, () => Array(m).fill(0) as number[]);
|
|
206
|
+
const result: number[][] = Array.from({ length: n }, () => Array(m).fill(0) as number[]);
|
|
197
207
|
|
|
198
208
|
for (let i = 0; i < n; i++) {
|
|
199
209
|
for (let j = 0; j < m; j++) {
|
|
@@ -207,7 +217,7 @@ function matrixMultiply(a: number[][], b: number[][]): number[][] {
|
|
|
207
217
|
|
|
208
218
|
const prevVal = resultRow[j];
|
|
209
219
|
if (prevVal !== undefined) {
|
|
210
|
-
resultRow[j] = prevVal +
|
|
220
|
+
resultRow[j] = prevVal + aVal * bVal;
|
|
211
221
|
}
|
|
212
222
|
}
|
|
213
223
|
}
|
|
@@ -218,10 +228,13 @@ function matrixMultiply(a: number[][], b: number[][]): number[][] {
|
|
|
218
228
|
|
|
219
229
|
/**
|
|
220
230
|
* Inflation step: element-wise powering and column normalization
|
|
231
|
+
* @param matrix - Input matrix to inflate
|
|
232
|
+
* @param inflation - Inflation parameter for element-wise powering
|
|
233
|
+
* @returns Inflated and column-normalized matrix
|
|
221
234
|
*/
|
|
222
235
|
function inflate(matrix: number[][], inflation: number): number[][] {
|
|
223
236
|
const n = matrix.length;
|
|
224
|
-
const result: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
|
|
237
|
+
const result: number[][] = Array.from({ length: n }, () => Array(n).fill(0) as number[]);
|
|
225
238
|
|
|
226
239
|
// Element-wise powering
|
|
227
240
|
for (let i = 0; i < n; i++) {
|
|
@@ -266,10 +279,13 @@ function inflate(matrix: number[][], inflation: number): number[][] {
|
|
|
266
279
|
|
|
267
280
|
/**
|
|
268
281
|
* Pruning step: remove small values
|
|
282
|
+
* @param matrix - Input matrix to prune
|
|
283
|
+
* @param threshold - Values below this threshold are set to zero
|
|
284
|
+
* @returns Pruned and re-normalized matrix
|
|
269
285
|
*/
|
|
270
286
|
function prune(matrix: number[][], threshold: number): number[][] {
|
|
271
287
|
const n = matrix.length;
|
|
272
|
-
const result: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
|
|
288
|
+
const result: number[][] = Array.from({ length: n }, () => Array(n).fill(0) as number[]);
|
|
273
289
|
|
|
274
290
|
for (let i = 0; i < n; i++) {
|
|
275
291
|
for (let j = 0; j < n; j++) {
|
|
@@ -323,6 +339,10 @@ function prune(matrix: number[][], threshold: number): number[][] {
|
|
|
323
339
|
|
|
324
340
|
/**
|
|
325
341
|
* Check if the algorithm has converged
|
|
342
|
+
* @param oldMatrix - Matrix from previous iteration
|
|
343
|
+
* @param newMatrix - Matrix from current iteration
|
|
344
|
+
* @param tolerance - Convergence tolerance threshold
|
|
345
|
+
* @returns True if the maximum difference between matrices is below tolerance
|
|
326
346
|
*/
|
|
327
347
|
function hasConverged(oldMatrix: number[][], newMatrix: number[][], tolerance: number): boolean {
|
|
328
348
|
const n = oldMatrix.length;
|
|
@@ -348,8 +368,14 @@ function hasConverged(oldMatrix: number[][], newMatrix: number[][], tolerance: n
|
|
|
348
368
|
|
|
349
369
|
/**
|
|
350
370
|
* Extract clusters from the final matrix
|
|
371
|
+
* @param matrix - Final converged MCL matrix
|
|
372
|
+
* @param nodeIds - Array of node IDs corresponding to matrix indices
|
|
373
|
+
* @returns Object containing communities and attractor nodes
|
|
351
374
|
*/
|
|
352
|
-
function extractClusters(
|
|
375
|
+
function extractClusters(
|
|
376
|
+
matrix: number[][],
|
|
377
|
+
nodeIds: NodeId[],
|
|
378
|
+
): {
|
|
353
379
|
communities: NodeId[][];
|
|
354
380
|
attractors: Set<NodeId>;
|
|
355
381
|
} {
|
|
@@ -391,10 +417,14 @@ function extractClusters(matrix: number[][], nodeIds: NodeId[]): {
|
|
|
391
417
|
}
|
|
392
418
|
|
|
393
419
|
if (clusterNodes.length > 0) {
|
|
394
|
-
communities.push(
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
420
|
+
communities.push(
|
|
421
|
+
clusterNodes
|
|
422
|
+
.map((idx) => {
|
|
423
|
+
const nodeId = nodeIds[idx];
|
|
424
|
+
return nodeId;
|
|
425
|
+
})
|
|
426
|
+
.filter((node): node is NodeId => node !== undefined),
|
|
427
|
+
);
|
|
398
428
|
clusterIndex++;
|
|
399
429
|
}
|
|
400
430
|
}
|
|
@@ -411,16 +441,16 @@ function extractClusters(matrix: number[][], nodeIds: NodeId[]): {
|
|
|
411
441
|
}
|
|
412
442
|
}
|
|
413
443
|
|
|
414
|
-
return {communities, attractors};
|
|
444
|
+
return { communities, attractors };
|
|
415
445
|
}
|
|
416
446
|
|
|
417
447
|
/**
|
|
418
448
|
* Calculate modularity of MCL clustering result
|
|
449
|
+
* @param graph - The original graph that was clustered
|
|
450
|
+
* @param communities - Array of communities (each community is an array of node IDs)
|
|
451
|
+
* @returns Modularity score between -0.5 and 1.0
|
|
419
452
|
*/
|
|
420
|
-
export function calculateMCLModularity(
|
|
421
|
-
graph: Graph,
|
|
422
|
-
communities: NodeId[][],
|
|
423
|
-
): number {
|
|
453
|
+
export function calculateMCLModularity(graph: Graph, communities: NodeId[][]): number {
|
|
424
454
|
const m = graph.totalEdgeCount;
|
|
425
455
|
if (m === 0) {
|
|
426
456
|
return 0;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {Graph} from "../core/graph.js";
|
|
2
|
-
import type {NodeId} from "../types/index.js";
|
|
3
|
-
import {euclideanDistance} from "../utils/math-utilities.js";
|
|
1
|
+
import type { Graph } from "../core/graph.js";
|
|
2
|
+
import type { NodeId } from "../types/index.js";
|
|
3
|
+
import { euclideanDistance } from "../utils/math-utilities.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Spectral Clustering implementation
|
|
@@ -29,24 +29,19 @@ export interface SpectralClusteringResult {
|
|
|
29
29
|
/**
|
|
30
30
|
* Perform spectral clustering on a graph
|
|
31
31
|
*
|
|
32
|
-
*
|
|
32
|
+
* Note: This implementation uses simplified power iteration for eigenvector
|
|
33
33
|
* computation with approximate eigenvalues. For production use cases requiring
|
|
34
34
|
* precise clustering, consider using a proper linear algebra library like ml-matrix.
|
|
35
35
|
*
|
|
36
36
|
* The approximate eigenvalues (0.1, 0.2 for second and third eigenvectors) work
|
|
37
37
|
* well for most graph structures but may produce suboptimal results for graphs
|
|
38
38
|
* with unusual spectral properties.
|
|
39
|
+
* @param graph - The input graph to cluster
|
|
40
|
+
* @param options - Spectral clustering options (k, laplacianType, etc.)
|
|
41
|
+
* @returns Spectral clustering result with communities and cluster assignments
|
|
39
42
|
*/
|
|
40
|
-
export function spectralClustering(
|
|
41
|
-
|
|
42
|
-
options: SpectralClusteringOptions,
|
|
43
|
-
): SpectralClusteringResult {
|
|
44
|
-
const {
|
|
45
|
-
k,
|
|
46
|
-
laplacianType = "normalized",
|
|
47
|
-
maxIterations = 100,
|
|
48
|
-
tolerance = 1e-4,
|
|
49
|
-
} = options;
|
|
43
|
+
export function spectralClustering(graph: Graph, options: SpectralClusteringOptions): SpectralClusteringResult {
|
|
44
|
+
const { k, laplacianType = "normalized", maxIterations = 100, tolerance = 1e-4 } = options;
|
|
50
45
|
|
|
51
46
|
// Input validation
|
|
52
47
|
if (k < 1 || !Number.isInteger(k)) {
|
|
@@ -62,7 +57,7 @@ export function spectralClustering(
|
|
|
62
57
|
const communities: NodeId[][] = nodeIds.map((id) => [id]);
|
|
63
58
|
const clusterAssignments = new Map<NodeId, number>();
|
|
64
59
|
nodeIds.forEach((id, index) => clusterAssignments.set(id, index));
|
|
65
|
-
return {communities, clusterAssignments};
|
|
60
|
+
return { communities, clusterAssignments };
|
|
66
61
|
}
|
|
67
62
|
|
|
68
63
|
// Build adjacency matrix
|
|
@@ -97,7 +92,7 @@ export function spectralClustering(
|
|
|
97
92
|
const kmeans = kMeansClustering(dataPoints, k, maxIterations, tolerance);
|
|
98
93
|
|
|
99
94
|
// Build communities
|
|
100
|
-
const communities: NodeId[][] = Array.from({length: k}, () => []);
|
|
95
|
+
const communities: NodeId[][] = Array.from({ length: k }, () => []);
|
|
101
96
|
const clusterAssignments = new Map<NodeId, number>();
|
|
102
97
|
|
|
103
98
|
for (let i = 0; i < nodeIds.length; i++) {
|
|
@@ -139,10 +134,13 @@ export function spectralClustering(
|
|
|
139
134
|
|
|
140
135
|
/**
|
|
141
136
|
* Build adjacency matrix from graph
|
|
137
|
+
* @param graph - The input graph
|
|
138
|
+
* @param nodeIds - Array of node IDs defining matrix index order
|
|
139
|
+
* @returns Square adjacency matrix with edge weights
|
|
142
140
|
*/
|
|
143
141
|
function buildAdjacencyMatrix(graph: Graph, nodeIds: NodeId[]): number[][] {
|
|
144
142
|
const n = nodeIds.length;
|
|
145
|
-
const matrix: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
|
|
143
|
+
const matrix: number[][] = Array.from({ length: n }, () => Array(n).fill(0) as number[]);
|
|
146
144
|
const nodeToIndex = new Map<NodeId, number>();
|
|
147
145
|
|
|
148
146
|
nodeIds.forEach((id, index) => nodeToIndex.set(id, index));
|
|
@@ -180,10 +178,13 @@ function buildAdjacencyMatrix(graph: Graph, nodeIds: NodeId[]): number[][] {
|
|
|
180
178
|
|
|
181
179
|
/**
|
|
182
180
|
* Build Laplacian matrix from adjacency matrix
|
|
181
|
+
* @param adjacency - Square adjacency matrix
|
|
182
|
+
* @param type - Type of Laplacian (unnormalized, normalized, or randomWalk)
|
|
183
|
+
* @returns The computed Laplacian matrix
|
|
183
184
|
*/
|
|
184
185
|
function buildLaplacianMatrix(adjacency: number[][], type: string): number[][] {
|
|
185
186
|
const n = adjacency.length;
|
|
186
|
-
const laplacian: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
|
|
187
|
+
const laplacian: number[][] = Array.from({ length: n }, () => Array(n).fill(0) as number[]);
|
|
187
188
|
|
|
188
189
|
// Calculate degree matrix
|
|
189
190
|
const degrees = Array(n).fill(0) as number[];
|
|
@@ -239,7 +240,14 @@ function buildLaplacianMatrix(adjacency: number[][], type: string): number[][] {
|
|
|
239
240
|
const adjacencyVal = adjacencyRow[j];
|
|
240
241
|
const di = degrees[i];
|
|
241
242
|
const dj = degrees[j];
|
|
242
|
-
if (
|
|
243
|
+
if (
|
|
244
|
+
adjacencyVal !== undefined &&
|
|
245
|
+
adjacencyVal > 0 &&
|
|
246
|
+
di !== undefined &&
|
|
247
|
+
dj !== undefined &&
|
|
248
|
+
di > 0 &&
|
|
249
|
+
dj > 0
|
|
250
|
+
) {
|
|
243
251
|
const laplacianRow = laplacian[i];
|
|
244
252
|
if (laplacianRow) {
|
|
245
253
|
laplacianRow[j] = -adjacencyVal / Math.sqrt(di * dj);
|
|
@@ -283,15 +291,21 @@ function buildLaplacianMatrix(adjacency: number[][], type: string): number[][] {
|
|
|
283
291
|
/**
|
|
284
292
|
* Find k smallest eigenvectors using simplified eigendecomposition
|
|
285
293
|
* This is a simplified implementation - in practice, you'd use LAPACK or similar
|
|
294
|
+
* @param matrix - The Laplacian matrix for eigendecomposition
|
|
295
|
+
* @param k - Number of smallest eigenvectors to find
|
|
296
|
+
* @returns Object containing eigenvalues and corresponding eigenvectors
|
|
286
297
|
*/
|
|
287
|
-
function findSmallestEigenvectors(
|
|
298
|
+
function findSmallestEigenvectors(
|
|
299
|
+
matrix: number[][],
|
|
300
|
+
k: number,
|
|
301
|
+
): {
|
|
288
302
|
eigenvalues: number[];
|
|
289
303
|
eigenvectors: number[][];
|
|
290
304
|
} {
|
|
291
305
|
const n = matrix.length;
|
|
292
306
|
|
|
293
307
|
if (n === 0 || k === 0) {
|
|
294
|
-
return {eigenvalues: [], eigenvectors: []};
|
|
308
|
+
return { eigenvalues: [], eigenvectors: [] };
|
|
295
309
|
}
|
|
296
310
|
|
|
297
311
|
// For very small matrices, use the full power iteration approach
|
|
@@ -314,10 +328,12 @@ function findSmallestEigenvectors(matrix: number[][], k: number): {
|
|
|
314
328
|
|
|
315
329
|
for (let eigIdx = 0; eigIdx < k; eigIdx++) {
|
|
316
330
|
// Initialize random vector
|
|
317
|
-
let vector = Array(n)
|
|
331
|
+
let vector = Array(n)
|
|
332
|
+
.fill(0)
|
|
333
|
+
.map(() => Math.random() - 0.5);
|
|
318
334
|
|
|
319
335
|
// Normalize initial vector
|
|
320
|
-
const initNorm = Math.sqrt(vector.reduce((sum, val) => sum +
|
|
336
|
+
const initNorm = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0));
|
|
321
337
|
if (initNorm > 0) {
|
|
322
338
|
vector = vector.map((val) => val / initNorm);
|
|
323
339
|
}
|
|
@@ -329,8 +345,8 @@ function findSmallestEigenvectors(matrix: number[][], k: number): {
|
|
|
329
345
|
continue;
|
|
330
346
|
}
|
|
331
347
|
|
|
332
|
-
const dot = vector.reduce((sum, val, idx) => sum +
|
|
333
|
-
vector = vector.map((val, idx) => val -
|
|
348
|
+
const dot = vector.reduce((sum, val, idx) => sum + val * (ejVector[idx] ?? 0), 0);
|
|
349
|
+
vector = vector.map((val, idx) => val - dot * (ejVector[idx] ?? 0));
|
|
334
350
|
}
|
|
335
351
|
|
|
336
352
|
// Power iteration
|
|
@@ -345,7 +361,7 @@ function findSmallestEigenvectors(matrix: number[][], k: number): {
|
|
|
345
361
|
if (matrixVal !== undefined && vecVal !== undefined) {
|
|
346
362
|
const nvVal = newVector[i];
|
|
347
363
|
if (nvVal !== undefined) {
|
|
348
|
-
newVector[i] = nvVal +
|
|
364
|
+
newVector[i] = nvVal + matrixVal * vecVal;
|
|
349
365
|
}
|
|
350
366
|
}
|
|
351
367
|
}
|
|
@@ -358,20 +374,20 @@ function findSmallestEigenvectors(matrix: number[][], k: number): {
|
|
|
358
374
|
continue;
|
|
359
375
|
}
|
|
360
376
|
|
|
361
|
-
const dot = newVector.reduce((sum, val, idx) => sum +
|
|
377
|
+
const dot = newVector.reduce((sum, val, idx) => sum + val * (ejVector[idx] ?? 0), 0);
|
|
362
378
|
for (let i = 0; i < n; i++) {
|
|
363
379
|
const ejVal = ejVector[i];
|
|
364
380
|
if (ejVal !== undefined) {
|
|
365
381
|
const nvVal = newVector[i];
|
|
366
382
|
if (nvVal !== undefined) {
|
|
367
|
-
newVector[i] = nvVal -
|
|
383
|
+
newVector[i] = nvVal - dot * ejVal;
|
|
368
384
|
}
|
|
369
385
|
}
|
|
370
386
|
}
|
|
371
387
|
}
|
|
372
388
|
|
|
373
389
|
// Normalize
|
|
374
|
-
const norm = Math.sqrt(newVector.reduce((sum, val) => sum +
|
|
390
|
+
const norm = Math.sqrt(newVector.reduce((sum, val) => sum + val * val, 0));
|
|
375
391
|
if (norm > 1e-10) {
|
|
376
392
|
vector = newVector.map((val) => val / norm);
|
|
377
393
|
} else {
|
|
@@ -390,29 +406,30 @@ function findSmallestEigenvectors(matrix: number[][], k: number): {
|
|
|
390
406
|
if (matrixVal !== undefined && vecVal !== undefined) {
|
|
391
407
|
const avVal = Av[i];
|
|
392
408
|
if (avVal !== undefined) {
|
|
393
|
-
Av[i] = avVal +
|
|
409
|
+
Av[i] = avVal + matrixVal * vecVal;
|
|
394
410
|
}
|
|
395
411
|
}
|
|
396
412
|
}
|
|
397
413
|
}
|
|
398
414
|
eigenvalue = vector.reduce((sum, val, idx) => {
|
|
399
415
|
const avVal = Av[idx];
|
|
400
|
-
return sum +
|
|
416
|
+
return sum + val * (avVal ?? 0);
|
|
401
417
|
}, 0);
|
|
402
418
|
|
|
403
419
|
eigenvectors.push(vector);
|
|
404
420
|
eigenvalues.push(eigenvalue);
|
|
405
421
|
}
|
|
406
422
|
|
|
407
|
-
return {eigenvalues, eigenvectors};
|
|
423
|
+
return { eigenvalues, eigenvectors };
|
|
408
424
|
}
|
|
409
425
|
|
|
410
426
|
/**
|
|
411
427
|
* Normalize matrix rows to unit length
|
|
428
|
+
* @param matrix - Matrix to normalize in place
|
|
412
429
|
*/
|
|
413
430
|
function normalizeRows(matrix: number[][]): void {
|
|
414
431
|
for (const row of matrix) {
|
|
415
|
-
const norm = Math.sqrt(row.reduce((sum, val) => sum +
|
|
432
|
+
const norm = Math.sqrt(row.reduce((sum, val) => sum + val * val, 0));
|
|
416
433
|
if (norm > 0) {
|
|
417
434
|
for (let j = 0; j < row.length; j++) {
|
|
418
435
|
const val = row[j];
|
|
@@ -426,25 +443,30 @@ function normalizeRows(matrix: number[][]): void {
|
|
|
426
443
|
|
|
427
444
|
/**
|
|
428
445
|
* K-means clustering algorithm
|
|
446
|
+
* @param data - Array of data points, each point is an array of coordinates
|
|
447
|
+
* @param k - Number of clusters to form
|
|
448
|
+
* @param maxIterations - Maximum number of iterations
|
|
449
|
+
* @param tolerance - Convergence tolerance for centroid movement
|
|
450
|
+
* @returns Object containing cluster assignments and final centroids
|
|
429
451
|
*/
|
|
430
452
|
function kMeansClustering(
|
|
431
453
|
data: number[][],
|
|
432
454
|
k: number,
|
|
433
455
|
maxIterations: number,
|
|
434
456
|
tolerance = 1e-4,
|
|
435
|
-
): {assignments: number[]
|
|
457
|
+
): { assignments: number[]; centroids: number[][] } {
|
|
436
458
|
const n = data.length;
|
|
437
459
|
const d = data[0]?.length ?? 0;
|
|
438
460
|
|
|
439
461
|
// Handle edge cases
|
|
440
462
|
if (n === 0 || k === 0) {
|
|
441
|
-
return {assignments: [], centroids: []};
|
|
463
|
+
return { assignments: [], centroids: [] };
|
|
442
464
|
}
|
|
443
465
|
|
|
444
466
|
if (k >= n) {
|
|
445
467
|
// Each point is its own cluster
|
|
446
468
|
return {
|
|
447
|
-
assignments: Array.from({length: n}, (_, i) => i),
|
|
469
|
+
assignments: Array.from({ length: n }, (_, i) => i),
|
|
448
470
|
centroids: data.slice(0, n),
|
|
449
471
|
};
|
|
450
472
|
}
|
|
@@ -457,7 +479,7 @@ function kMeansClustering(
|
|
|
457
479
|
const idx = Math.floor(Math.random() * n);
|
|
458
480
|
if (!selectedIndices.has(idx) && data[idx]) {
|
|
459
481
|
selectedIndices.add(idx);
|
|
460
|
-
centroids.push([...
|
|
482
|
+
centroids.push([...data[idx]]);
|
|
461
483
|
}
|
|
462
484
|
}
|
|
463
485
|
|
|
@@ -509,14 +531,14 @@ function kMeansClustering(
|
|
|
509
531
|
break;
|
|
510
532
|
}
|
|
511
533
|
|
|
512
|
-
oldAssignments = [...
|
|
534
|
+
oldAssignments = [...assignments];
|
|
513
535
|
|
|
514
536
|
// Store old centroids for tolerance-based convergence check
|
|
515
|
-
const oldCentroids = centroids.map((c) => [...
|
|
537
|
+
const oldCentroids = centroids.map((c) => [...c]);
|
|
516
538
|
|
|
517
539
|
// Update centroids
|
|
518
540
|
const counts = Array(k).fill(0) as number[];
|
|
519
|
-
const sums: number[][] = Array.from({length: k}, () => Array(d).fill(0) as number[]);
|
|
541
|
+
const sums: number[][] = Array.from({ length: k }, () => Array(d).fill(0) as number[]);
|
|
520
542
|
|
|
521
543
|
for (let i = 0; i < n; i++) {
|
|
522
544
|
const cluster = assignments[i] ?? 0;
|
|
@@ -581,13 +603,21 @@ function kMeansClustering(
|
|
|
581
603
|
}
|
|
582
604
|
}
|
|
583
605
|
|
|
584
|
-
return {assignments, centroids};
|
|
606
|
+
return { assignments, centroids };
|
|
585
607
|
}
|
|
586
608
|
|
|
587
609
|
/**
|
|
588
610
|
* Compute smallest eigenvectors for small k (optimized for k=2, k=3)
|
|
611
|
+
* @param matrix - The Laplacian matrix
|
|
612
|
+
* @param k - Number of eigenvectors to compute (1-3)
|
|
613
|
+
* @param n - Size of the matrix
|
|
614
|
+
* @returns Object containing eigenvalues and eigenvectors
|
|
589
615
|
*/
|
|
590
|
-
function computeSmallestEigenvectorsSimple(
|
|
616
|
+
function computeSmallestEigenvectorsSimple(
|
|
617
|
+
matrix: number[][],
|
|
618
|
+
k: number,
|
|
619
|
+
n: number,
|
|
620
|
+
): {
|
|
591
621
|
eigenvalues: number[];
|
|
592
622
|
eigenvectors: number[][];
|
|
593
623
|
} {
|
|
@@ -603,11 +633,13 @@ function computeSmallestEigenvectorsSimple(matrix: number[][], k: number, n: num
|
|
|
603
633
|
if (k >= 2) {
|
|
604
634
|
// Use power iteration on I - L/lambda_max to find second smallest
|
|
605
635
|
const maxEig = 2; // For normalized Laplacian, max eigenvalue <= 2
|
|
606
|
-
let vector = Array(n)
|
|
636
|
+
let vector = Array(n)
|
|
637
|
+
.fill(0)
|
|
638
|
+
.map(() => Math.random() - 0.5);
|
|
607
639
|
|
|
608
640
|
// Make orthogonal to first eigenvector
|
|
609
|
-
const dot1 = vector.reduce((sum, val) => sum +
|
|
610
|
-
vector = vector.map((val) => val -
|
|
641
|
+
const dot1 = vector.reduce((sum, val) => sum + val / Math.sqrt(n), 0);
|
|
642
|
+
vector = vector.map((val) => val - dot1 / Math.sqrt(n));
|
|
611
643
|
|
|
612
644
|
// Power iteration on shifted matrix
|
|
613
645
|
for (let iter = 0; iter < 100; iter++) {
|
|
@@ -624,18 +656,18 @@ function computeSmallestEigenvectorsSimple(matrix: number[][], k: number, n: num
|
|
|
624
656
|
for (let j = 0; j < n; j++) {
|
|
625
657
|
const matrixVal = matrix[i]?.[j] ?? 0;
|
|
626
658
|
const vecVal = vector[j] ?? 0;
|
|
627
|
-
newVector[i] = (newVector[i] ?? 0) - (
|
|
659
|
+
newVector[i] = (newVector[i] ?? 0) - (matrixVal * vecVal) / maxEig;
|
|
628
660
|
}
|
|
629
661
|
}
|
|
630
662
|
|
|
631
663
|
// Orthogonalize against first eigenvector
|
|
632
|
-
const dot = newVector.reduce((sum, val) => sum +
|
|
664
|
+
const dot = newVector.reduce((sum, val) => sum + val / Math.sqrt(n), 0);
|
|
633
665
|
for (let i = 0; i < n; i++) {
|
|
634
|
-
newVector[i] = (newVector[i] ?? 0) -
|
|
666
|
+
newVector[i] = (newVector[i] ?? 0) - dot / Math.sqrt(n);
|
|
635
667
|
}
|
|
636
668
|
|
|
637
669
|
// Normalize
|
|
638
|
-
const norm = Math.sqrt(newVector.reduce((sum, val) => sum +
|
|
670
|
+
const norm = Math.sqrt(newVector.reduce((sum, val) => sum + val * val, 0));
|
|
639
671
|
if (norm > 1e-10) {
|
|
640
672
|
vector = newVector.map((val) => val / norm);
|
|
641
673
|
}
|
|
@@ -647,12 +679,14 @@ function computeSmallestEigenvectorsSimple(matrix: number[][], k: number, n: num
|
|
|
647
679
|
|
|
648
680
|
// For k = 3, add another eigenvector
|
|
649
681
|
if (k >= 3) {
|
|
650
|
-
let vector = Array(n)
|
|
682
|
+
let vector = Array(n)
|
|
683
|
+
.fill(0)
|
|
684
|
+
.map(() => Math.random() - 0.5);
|
|
651
685
|
|
|
652
686
|
// Orthogonalize against previous eigenvectors
|
|
653
687
|
for (const prev of eigenvectors) {
|
|
654
|
-
const dot = vector.reduce((sum, val, idx) => sum +
|
|
655
|
-
vector = vector.map((val, idx) => val -
|
|
688
|
+
const dot = vector.reduce((sum, val, idx) => sum + val * (prev[idx] ?? 0), 0);
|
|
689
|
+
vector = vector.map((val, idx) => val - dot * (prev[idx] ?? 0));
|
|
656
690
|
}
|
|
657
691
|
|
|
658
692
|
// Similar power iteration
|
|
@@ -669,20 +703,20 @@ function computeSmallestEigenvectorsSimple(matrix: number[][], k: number, n: num
|
|
|
669
703
|
for (let j = 0; j < n; j++) {
|
|
670
704
|
const matrixVal = matrix[i]?.[j] ?? 0;
|
|
671
705
|
const vecVal = vector[j] ?? 0;
|
|
672
|
-
newVector[i] = (newVector[i] ?? 0) - (
|
|
706
|
+
newVector[i] = (newVector[i] ?? 0) - (matrixVal * vecVal) / 2;
|
|
673
707
|
}
|
|
674
708
|
}
|
|
675
709
|
|
|
676
710
|
// Orthogonalize
|
|
677
711
|
for (const prev of eigenvectors) {
|
|
678
|
-
const dot = newVector.reduce((sum, val, idx) => sum +
|
|
712
|
+
const dot = newVector.reduce((sum, val, idx) => sum + val * (prev[idx] ?? 0), 0);
|
|
679
713
|
for (let i = 0; i < n; i++) {
|
|
680
|
-
newVector[i] = (newVector[i] ?? 0) -
|
|
714
|
+
newVector[i] = (newVector[i] ?? 0) - dot * (prev[i] ?? 0);
|
|
681
715
|
}
|
|
682
716
|
}
|
|
683
717
|
|
|
684
718
|
// Normalize
|
|
685
|
-
const norm = Math.sqrt(newVector.reduce((sum, val) => sum +
|
|
719
|
+
const norm = Math.sqrt(newVector.reduce((sum, val) => sum + val * val, 0));
|
|
686
720
|
if (norm > 1e-10) {
|
|
687
721
|
vector = newVector.map((val) => val / norm);
|
|
688
722
|
}
|
|
@@ -692,5 +726,5 @@ function computeSmallestEigenvectorsSimple(matrix: number[][], k: number, n: num
|
|
|
692
726
|
eigenvalues.push(0.2); // Approximate
|
|
693
727
|
}
|
|
694
728
|
|
|
695
|
-
return {eigenvalues: eigenvalues.slice(0, k), eigenvectors: eigenvectors.slice(0, k)};
|
|
729
|
+
return { eigenvalues: eigenvalues.slice(0, k), eigenvectors: eigenvectors.slice(0, k) };
|
|
696
730
|
}
|