@graphty/algorithms 1.0.1 → 1.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 +1259 -61
- package/dist/algorithms.d.ts +2 -0
- package/dist/algorithms.d.ts.map +1 -0
- package/dist/algorithms.js +2 -0
- package/dist/algorithms.js.map +1 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +22 -7
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +7 -7
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +1 -1
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.d.ts +27 -0
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/eigenvector.js +113 -0
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -0
- package/dist/src/algorithms/centrality/hits.d.ts +35 -0
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/hits.js +146 -0
- package/dist/src/algorithms/centrality/hits.js.map +1 -0
- package/dist/src/algorithms/centrality/index.d.ts +6 -0
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +3 -0
- package/dist/src/algorithms/centrality/index.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +30 -0
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/katz.js +85 -0
- package/dist/src/algorithms/centrality/katz.js.map +1 -0
- package/dist/src/algorithms/centrality/pagerank.js +3 -3
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts +23 -0
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -0
- package/dist/src/algorithms/community/girvan-newman.js +297 -0
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -0
- package/dist/src/algorithms/community/index.d.ts +12 -0
- package/dist/src/algorithms/community/index.d.ts.map +1 -0
- package/dist/src/algorithms/community/index.js +12 -0
- package/dist/src/algorithms/community/index.js.map +1 -0
- package/dist/src/algorithms/community/label-propagation.d.ts +42 -0
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -0
- package/dist/src/algorithms/community/label-propagation.js +307 -0
- package/dist/src/algorithms/community/label-propagation.js.map +1 -0
- package/dist/src/algorithms/community/leiden.d.ts +33 -0
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -0
- package/dist/src/algorithms/community/leiden.js +434 -0
- package/dist/src/algorithms/community/leiden.js.map +1 -0
- package/dist/src/algorithms/community/louvain.d.ts +19 -0
- package/dist/src/algorithms/community/louvain.d.ts.map +1 -0
- package/dist/src/algorithms/community/louvain.js +219 -0
- package/dist/src/algorithms/community/louvain.js.map +1 -0
- package/dist/src/algorithms/components/connected.js +7 -7
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/index.d.ts +7 -0
- package/dist/src/algorithms/index.d.ts.map +1 -1
- package/dist/src/algorithms/index.js +14 -0
- package/dist/src/algorithms/index.js.map +1 -1
- package/dist/src/algorithms/matching/bipartite.d.ts +37 -0
- package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -0
- package/dist/src/algorithms/matching/bipartite.js +127 -0
- package/dist/src/algorithms/matching/bipartite.js.map +1 -0
- package/dist/src/algorithms/matching/index.d.ts +8 -0
- package/dist/src/algorithms/matching/index.d.ts.map +1 -0
- package/dist/src/algorithms/matching/index.js +6 -0
- package/dist/src/algorithms/matching/index.js.map +1 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts +32 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -0
- package/dist/src/algorithms/matching/isomorphism.js +291 -0
- package/dist/src/algorithms/matching/isomorphism.js.map +1 -0
- package/dist/src/algorithms/mst/index.d.ts +4 -0
- package/dist/src/algorithms/mst/index.d.ts.map +1 -0
- package/dist/src/algorithms/mst/index.js +3 -0
- package/dist/src/algorithms/mst/index.js.map +1 -0
- package/dist/src/algorithms/mst/kruskal.d.ts +9 -0
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -0
- package/dist/src/algorithms/mst/kruskal.js +43 -0
- package/dist/src/algorithms/mst/kruskal.js.map +1 -0
- package/dist/src/algorithms/mst/prim.d.ts +5 -0
- package/dist/src/algorithms/mst/prim.d.ts.map +1 -0
- package/dist/src/algorithms/mst/prim.js +63 -0
- package/dist/src/algorithms/mst/prim.js.map +1 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.js +2 -2
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +5 -5
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +14 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +129 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -0
- package/dist/src/algorithms/shortest-path/index.d.ts +2 -0
- package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/index.js +1 -0
- package/dist/src/algorithms/shortest-path/index.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +2 -2
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +11 -11
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/clustering/hierarchical.d.ts +48 -0
- package/dist/src/clustering/hierarchical.d.ts.map +1 -0
- package/dist/src/clustering/hierarchical.js +439 -0
- package/dist/src/clustering/hierarchical.js.map +1 -0
- package/dist/src/clustering/index.d.ts +7 -0
- package/dist/src/clustering/index.d.ts.map +1 -0
- package/dist/src/clustering/index.js +5 -0
- package/dist/src/clustering/index.js.map +1 -0
- package/dist/src/clustering/k-core.d.ts +63 -0
- package/dist/src/clustering/k-core.d.ts.map +1 -0
- package/dist/src/clustering/k-core.js +377 -0
- package/dist/src/clustering/k-core.js.map +1 -0
- package/dist/src/clustering/mcl.d.ts +36 -0
- package/dist/src/clustering/mcl.d.ts.map +1 -0
- package/dist/src/clustering/mcl.js +343 -0
- package/dist/src/clustering/mcl.js.map +1 -0
- package/dist/src/clustering/spectral.d.ts +28 -0
- package/dist/src/clustering/spectral.d.ts.map +1 -0
- package/dist/src/clustering/spectral.js +551 -0
- package/dist/src/clustering/spectral.js.map +1 -0
- package/dist/src/core/graph.d.ts +9 -0
- package/dist/src/core/graph.d.ts.map +1 -1
- package/dist/src/core/graph.js +31 -11
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/data-structures/priority-queue.js +0 -2
- package/dist/src/data-structures/priority-queue.js.map +1 -1
- package/dist/src/data-structures/union-find.js +2 -5
- package/dist/src/data-structures/union-find.js.map +1 -1
- package/dist/src/flow/ford-fulkerson.d.ts +53 -0
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -0
- package/dist/src/flow/ford-fulkerson.js +355 -0
- package/dist/src/flow/ford-fulkerson.js.map +1 -0
- package/dist/src/flow/index.d.ts +3 -0
- package/dist/src/flow/index.d.ts.map +1 -0
- package/dist/src/flow/index.js +3 -0
- package/dist/src/flow/index.js.map +1 -0
- package/dist/src/flow/min-cut.d.ts +49 -0
- package/dist/src/flow/min-cut.d.ts.map +1 -0
- package/dist/src/flow/min-cut.js +383 -0
- package/dist/src/flow/min-cut.js.map +1 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/link-prediction/adamic-adar.d.ts +56 -0
- package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -0
- package/dist/src/link-prediction/adamic-adar.js +256 -0
- package/dist/src/link-prediction/adamic-adar.js.map +1 -0
- package/dist/src/link-prediction/common-neighbors.d.ts +50 -0
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -0
- package/dist/src/link-prediction/common-neighbors.js +155 -0
- package/dist/src/link-prediction/common-neighbors.js.map +1 -0
- package/dist/src/link-prediction/index.d.ts +7 -0
- package/dist/src/link-prediction/index.d.ts.map +1 -0
- package/dist/src/link-prediction/index.js +6 -0
- package/dist/src/link-prediction/index.js.map +1 -0
- package/dist/src/pathfinding/astar.d.ts +50 -0
- package/dist/src/pathfinding/astar.d.ts.map +1 -0
- package/dist/src/pathfinding/astar.js +180 -0
- package/dist/src/pathfinding/astar.js.map +1 -0
- package/dist/src/pathfinding/index.d.ts +3 -0
- package/dist/src/pathfinding/index.d.ts.map +1 -0
- package/dist/src/pathfinding/index.js +3 -0
- package/dist/src/pathfinding/index.js.map +1 -0
- package/dist/src/pathfinding/utils.d.ts +18 -0
- package/dist/src/pathfinding/utils.d.ts.map +1 -0
- package/dist/src/pathfinding/utils.js +68 -0
- package/dist/src/pathfinding/utils.js.map +1 -0
- package/dist/src/research/grsbm.d.ts +83 -0
- package/dist/src/research/grsbm.d.ts.map +1 -0
- package/dist/src/research/grsbm.js +411 -0
- package/dist/src/research/grsbm.js.map +1 -0
- package/dist/src/research/index.d.ts +12 -0
- package/dist/src/research/index.d.ts.map +1 -0
- package/dist/src/research/index.js +14 -0
- package/dist/src/research/index.js.map +1 -0
- package/dist/src/research/sync.d.ts +49 -0
- package/dist/src/research/sync.d.ts.map +1 -0
- package/dist/src/research/sync.js +342 -0
- package/dist/src/research/sync.js.map +1 -0
- package/dist/src/research/terahac.d.ts +63 -0
- package/dist/src/research/terahac.d.ts.map +1 -0
- package/dist/src/research/terahac.js +375 -0
- package/dist/src/research/terahac.js.map +1 -0
- package/dist/src/types/index.d.ts +9 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/utils/graphNode.d.ts +8 -0
- package/dist/src/utils/graphNode.d.ts.map +1 -0
- package/dist/src/utils/graphNode.js +2 -0
- package/dist/src/utils/graphNode.js.map +1 -0
- package/dist/src/utils/priorityQueue.d.ts +21 -0
- package/dist/src/utils/priorityQueue.d.ts.map +1 -0
- package/dist/src/utils/priorityQueue.js +101 -0
- package/dist/src/utils/priorityQueue.js.map +1 -0
- package/package.json +13 -2
- package/src/algorithms/centrality/betweenness.ts +25 -7
- package/src/algorithms/centrality/closeness.ts +7 -7
- package/src/algorithms/centrality/degree.ts +1 -1
- package/src/algorithms/centrality/eigenvector.ts +162 -0
- package/src/algorithms/centrality/hits.ts +210 -0
- package/src/algorithms/centrality/index.ts +6 -0
- package/src/algorithms/centrality/katz.ts +139 -0
- package/src/algorithms/centrality/pagerank.ts +3 -3
- package/src/algorithms/community/girvan-newman.ts +372 -0
- package/src/algorithms/community/index.ts +13 -0
- package/src/algorithms/community/label-propagation.ts +392 -0
- package/src/algorithms/community/leiden.ts +572 -0
- package/src/algorithms/community/louvain.ts +298 -0
- package/src/algorithms/components/connected.ts +7 -7
- package/src/algorithms/index.ts +21 -0
- package/src/algorithms/matching/bipartite.ts +179 -0
- package/src/algorithms/matching/index.ts +8 -0
- package/src/algorithms/matching/isomorphism.ts +392 -0
- package/src/algorithms/mst/index.ts +4 -0
- package/src/algorithms/mst/kruskal.ts +61 -0
- package/src/algorithms/mst/prim.ts +80 -0
- package/src/algorithms/shortest-path/bellman-ford.ts +2 -2
- package/src/algorithms/shortest-path/dijkstra.ts +5 -5
- package/src/algorithms/shortest-path/floyd-warshall.ts +170 -0
- package/src/algorithms/shortest-path/index.ts +2 -0
- package/src/algorithms/traversal/bfs.ts +2 -2
- package/src/algorithms/traversal/dfs.ts +11 -11
- package/src/clustering/hierarchical.ts +555 -0
- package/src/clustering/index.ts +6 -0
- package/src/clustering/k-core.ts +460 -0
- package/src/clustering/mcl.ts +454 -0
- package/src/clustering/spectral.ts +672 -0
- package/src/core/graph.ts +34 -6
- package/src/data-structures/union-find.ts +2 -2
- package/src/flow/ford-fulkerson.ts +459 -0
- package/src/flow/index.ts +2 -0
- package/src/flow/min-cut.ts +482 -0
- package/src/index.ts +5 -0
- package/src/link-prediction/adamic-adar.ts +353 -0
- package/src/link-prediction/common-neighbors.ts +252 -0
- package/src/link-prediction/index.ts +20 -0
- package/src/pathfinding/astar.ts +229 -0
- package/src/pathfinding/index.ts +2 -0
- package/src/pathfinding/utils.ts +87 -0
- package/src/research/grsbm.ts +589 -0
- package/src/research/index.ts +17 -0
- package/src/research/sync.ts +469 -0
- package/src/research/terahac.ts +506 -0
- package/src/types/index.ts +11 -0
- package/src/utils/graphNode.ts +7 -0
- package/src/utils/priorityQueue.ts +120 -0
- package/dist/test/browser/basic.test.d.ts +0 -2
- package/dist/test/browser/basic.test.d.ts.map +0 -1
- package/dist/test/browser/basic.test.js +0 -70
- package/dist/test/browser/basic.test.js.map +0 -1
- package/dist/test/unit/bellman-ford.test.d.ts +0 -2
- package/dist/test/unit/bellman-ford.test.d.ts.map +0 -1
- package/dist/test/unit/bellman-ford.test.js +0 -231
- package/dist/test/unit/bellman-ford.test.js.map +0 -1
- package/dist/test/unit/betweenness-centrality.test.d.ts +0 -2
- package/dist/test/unit/betweenness-centrality.test.d.ts.map +0 -1
- package/dist/test/unit/betweenness-centrality.test.js +0 -311
- package/dist/test/unit/betweenness-centrality.test.js.map +0 -1
- package/dist/test/unit/bfs.test.d.ts +0 -2
- package/dist/test/unit/bfs.test.d.ts.map +0 -1
- package/dist/test/unit/bfs.test.js +0 -277
- package/dist/test/unit/bfs.test.js.map +0 -1
- package/dist/test/unit/closeness.test.d.ts +0 -2
- package/dist/test/unit/closeness.test.d.ts.map +0 -1
- package/dist/test/unit/closeness.test.js +0 -266
- package/dist/test/unit/closeness.test.js.map +0 -1
- package/dist/test/unit/connected-components.test.d.ts +0 -2
- package/dist/test/unit/connected-components.test.d.ts.map +0 -1
- package/dist/test/unit/connected-components.test.js +0 -355
- package/dist/test/unit/connected-components.test.js.map +0 -1
- package/dist/test/unit/degree-centrality.test.d.ts +0 -2
- package/dist/test/unit/degree-centrality.test.d.ts.map +0 -1
- package/dist/test/unit/degree-centrality.test.js +0 -260
- package/dist/test/unit/degree-centrality.test.js.map +0 -1
- package/dist/test/unit/dfs.test.d.ts +0 -2
- package/dist/test/unit/dfs.test.d.ts.map +0 -1
- package/dist/test/unit/dfs.test.js +0 -350
- package/dist/test/unit/dfs.test.js.map +0 -1
- package/dist/test/unit/dijkstra.test.d.ts +0 -2
- package/dist/test/unit/dijkstra.test.d.ts.map +0 -1
- package/dist/test/unit/dijkstra.test.js +0 -311
- package/dist/test/unit/dijkstra.test.js.map +0 -1
- package/dist/test/unit/graph.test.d.ts +0 -2
- package/dist/test/unit/graph.test.d.ts.map +0 -1
- package/dist/test/unit/graph.test.js +0 -175
- package/dist/test/unit/graph.test.js.map +0 -1
- package/dist/test/unit/pagerank.test.d.ts +0 -2
- package/dist/test/unit/pagerank.test.d.ts.map +0 -1
- package/dist/test/unit/pagerank.test.js +0 -281
- package/dist/test/unit/pagerank.test.js.map +0 -1
- package/dist/test/unit/priority-queue.test.d.ts +0 -2
- package/dist/test/unit/priority-queue.test.d.ts.map +0 -1
- package/dist/test/unit/priority-queue.test.js +0 -189
- package/dist/test/unit/priority-queue.test.js.map +0 -1
- package/dist/test/unit/union-find.test.d.ts +0 -2
- package/dist/test/unit/union-find.test.d.ts.map +0 -1
- package/dist/test/unit/union-find.test.js +0 -245
- package/dist/test/unit/union-find.test.js.map +0 -1
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
import type {Graph} from "../core/graph.js";
|
|
2
|
+
import type {NodeId} from "../types/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Spectral Clustering implementation
|
|
6
|
+
*
|
|
7
|
+
* Uses eigenvalues and eigenvectors of the graph Laplacian matrix to perform clustering.
|
|
8
|
+
* Particularly effective for finding non-convex clusters and communities in graphs.
|
|
9
|
+
*
|
|
10
|
+
* Time complexity: O(V³) for eigendecomposition
|
|
11
|
+
* Space complexity: O(V²)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface SpectralClusteringOptions {
|
|
15
|
+
k: number; // Number of clusters to find
|
|
16
|
+
laplacianType?: "unnormalized" | "normalized" | "randomWalk"; // Type of Laplacian
|
|
17
|
+
maxIterations?: number; // Max iterations for k-means (default: 100)
|
|
18
|
+
tolerance?: number; // Convergence tolerance (default: 1e-4)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SpectralClusteringResult {
|
|
22
|
+
communities: NodeId[][];
|
|
23
|
+
clusterAssignments: Map<NodeId, number>;
|
|
24
|
+
eigenvalues?: number[];
|
|
25
|
+
eigenvectors?: number[][];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Perform spectral clustering on a graph
|
|
30
|
+
*/
|
|
31
|
+
export function spectralClustering(
|
|
32
|
+
graph: Graph,
|
|
33
|
+
options: SpectralClusteringOptions,
|
|
34
|
+
): SpectralClusteringResult {
|
|
35
|
+
const {
|
|
36
|
+
k,
|
|
37
|
+
laplacianType = "normalized",
|
|
38
|
+
maxIterations = 100,
|
|
39
|
+
} = options;
|
|
40
|
+
|
|
41
|
+
const nodes = Array.from(graph.nodes());
|
|
42
|
+
const nodeIds = nodes.map((node) => node.id);
|
|
43
|
+
const n = nodeIds.length;
|
|
44
|
+
|
|
45
|
+
if (k >= n) {
|
|
46
|
+
// Return each node as its own cluster
|
|
47
|
+
const communities: NodeId[][] = nodeIds.map((id) => [id]);
|
|
48
|
+
const clusterAssignments = new Map<NodeId, number>();
|
|
49
|
+
nodeIds.forEach((id, index) => clusterAssignments.set(id, index));
|
|
50
|
+
return {communities, clusterAssignments};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Build adjacency matrix
|
|
54
|
+
const adjacencyMatrix = buildAdjacencyMatrix(graph, nodeIds);
|
|
55
|
+
|
|
56
|
+
// Build Laplacian matrix
|
|
57
|
+
const laplacianMatrix = buildLaplacianMatrix(adjacencyMatrix, laplacianType);
|
|
58
|
+
|
|
59
|
+
// Find k smallest eigenvectors
|
|
60
|
+
const eigenResult = findSmallestEigenvectors(laplacianMatrix, k);
|
|
61
|
+
|
|
62
|
+
// Perform k-means clustering on the eigenvectors
|
|
63
|
+
// For spectral clustering, we need to transpose the eigenvector matrix
|
|
64
|
+
// Each row should be a data point (node) with features from the eigenvectors
|
|
65
|
+
const dataPoints: number[][] = [];
|
|
66
|
+
for (let i = 0; i < nodeIds.length; i++) {
|
|
67
|
+
const point: number[] = [];
|
|
68
|
+
for (let j = 0; j < k; j++) {
|
|
69
|
+
const eigenvector = eigenResult.eigenvectors[j];
|
|
70
|
+
if (eigenvector) {
|
|
71
|
+
point.push(eigenvector[i] ?? 0);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
dataPoints.push(point);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Normalize the data points row-wise (for normalized spectral clustering)
|
|
78
|
+
if (laplacianType === "normalized") {
|
|
79
|
+
normalizeRows(dataPoints);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const kmeans = kMeansClustering(dataPoints, k, maxIterations);
|
|
83
|
+
|
|
84
|
+
// Build communities
|
|
85
|
+
const communities: NodeId[][] = Array.from({length: k}, () => []);
|
|
86
|
+
const clusterAssignments = new Map<NodeId, number>();
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < nodeIds.length; i++) {
|
|
89
|
+
const clusterId = kmeans.assignments[i] ?? 0;
|
|
90
|
+
const nodeId = nodeIds[i];
|
|
91
|
+
if (!nodeId) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Ensure clusterId is valid
|
|
96
|
+
if (clusterId >= 0 && clusterId < k) {
|
|
97
|
+
const community = communities[clusterId];
|
|
98
|
+
if (community) {
|
|
99
|
+
community.push(nodeId);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
clusterAssignments.set(nodeId, clusterId);
|
|
103
|
+
} else {
|
|
104
|
+
// Assign to first cluster if invalid
|
|
105
|
+
const firstCommunity = communities[0];
|
|
106
|
+
if (firstCommunity) {
|
|
107
|
+
firstCommunity.push(nodeId);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
clusterAssignments.set(nodeId, 0);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Filter out empty communities
|
|
115
|
+
const nonEmptyCommunities = communities.filter((community) => community.length > 0);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
communities: nonEmptyCommunities,
|
|
119
|
+
clusterAssignments,
|
|
120
|
+
eigenvalues: eigenResult.eigenvalues,
|
|
121
|
+
eigenvectors: eigenResult.eigenvectors,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build adjacency matrix from graph
|
|
127
|
+
*/
|
|
128
|
+
function buildAdjacencyMatrix(graph: Graph, nodeIds: NodeId[]): number[][] {
|
|
129
|
+
const n = nodeIds.length;
|
|
130
|
+
const matrix: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
|
|
131
|
+
const nodeToIndex = new Map<NodeId, number>();
|
|
132
|
+
|
|
133
|
+
nodeIds.forEach((id, index) => nodeToIndex.set(id, index));
|
|
134
|
+
|
|
135
|
+
for (let i = 0; i < n; i++) {
|
|
136
|
+
const nodeId = nodeIds[i];
|
|
137
|
+
if (!nodeId) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const neighbors = graph.neighbors(nodeId);
|
|
142
|
+
|
|
143
|
+
for (const neighbor of neighbors) {
|
|
144
|
+
const j = nodeToIndex.get(neighbor);
|
|
145
|
+
if (j !== undefined) {
|
|
146
|
+
const edge = graph.getEdge(nodeId, neighbor);
|
|
147
|
+
const weight = edge?.weight ?? 1;
|
|
148
|
+
const matrixRow = matrix[i];
|
|
149
|
+
if (matrixRow) {
|
|
150
|
+
matrixRow[j] = weight;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!graph.isDirected) {
|
|
154
|
+
const matrixRowJ = matrix[j];
|
|
155
|
+
if (matrixRowJ) {
|
|
156
|
+
matrixRowJ[i] = weight;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return matrix;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Build Laplacian matrix from adjacency matrix
|
|
168
|
+
*/
|
|
169
|
+
function buildLaplacianMatrix(adjacency: number[][], type: string): number[][] {
|
|
170
|
+
const n = adjacency.length;
|
|
171
|
+
const laplacian: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
|
|
172
|
+
|
|
173
|
+
// Calculate degree matrix
|
|
174
|
+
const degrees = Array(n).fill(0) as number[];
|
|
175
|
+
for (let i = 0; i < n; i++) {
|
|
176
|
+
for (let j = 0; j < n; j++) {
|
|
177
|
+
const adjacencyRow = adjacency[i];
|
|
178
|
+
const adjacencyVal = adjacencyRow ? adjacencyRow[j] : 0;
|
|
179
|
+
if (adjacencyVal !== undefined) {
|
|
180
|
+
const degreeVal = degrees[i];
|
|
181
|
+
if (degreeVal !== undefined) {
|
|
182
|
+
degrees[i] = degreeVal + adjacencyVal;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (type === "unnormalized") {
|
|
189
|
+
// L = D - A
|
|
190
|
+
for (let i = 0; i < n; i++) {
|
|
191
|
+
for (let j = 0; j < n; j++) {
|
|
192
|
+
if (i === j) {
|
|
193
|
+
const laplacianRow = laplacian[i];
|
|
194
|
+
const degreeVal = degrees[i];
|
|
195
|
+
if (laplacianRow && degreeVal !== undefined) {
|
|
196
|
+
laplacianRow[j] = degreeVal;
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
const adjacencyRow = adjacency[i];
|
|
200
|
+
const laplacianRow = laplacian[i];
|
|
201
|
+
if (adjacencyRow && laplacianRow) {
|
|
202
|
+
const adjacencyVal = adjacencyRow[j];
|
|
203
|
+
laplacianRow[j] = adjacencyVal !== undefined ? -adjacencyVal : 0;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} else if (type === "normalized") {
|
|
209
|
+
// L_sym = D^(-1/2) * L * D^(-1/2)
|
|
210
|
+
for (let i = 0; i < n; i++) {
|
|
211
|
+
for (let j = 0; j < n; j++) {
|
|
212
|
+
if (i === j) {
|
|
213
|
+
const di = degrees[i];
|
|
214
|
+
const laplacianRow = laplacian[i];
|
|
215
|
+
if (laplacianRow) {
|
|
216
|
+
laplacianRow[j] = di !== undefined && di > 0 ? 1 : 0;
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
const adjacencyRow = adjacency[i];
|
|
220
|
+
if (!adjacencyRow) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const adjacencyVal = adjacencyRow[j];
|
|
225
|
+
const di = degrees[i];
|
|
226
|
+
const dj = degrees[j];
|
|
227
|
+
if (adjacencyVal !== undefined && adjacencyVal > 0 && di !== undefined && dj !== undefined && di > 0 && dj > 0) {
|
|
228
|
+
const laplacianRow = laplacian[i];
|
|
229
|
+
if (laplacianRow) {
|
|
230
|
+
laplacianRow[j] = -adjacencyVal / Math.sqrt(di * dj);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} else if (type === "randomWalk") {
|
|
237
|
+
// L_rw = D^(-1) * L
|
|
238
|
+
for (let i = 0; i < n; i++) {
|
|
239
|
+
for (let j = 0; j < n; j++) {
|
|
240
|
+
if (i === j) {
|
|
241
|
+
const di = degrees[i];
|
|
242
|
+
const laplacianRow = laplacian[i];
|
|
243
|
+
if (laplacianRow) {
|
|
244
|
+
laplacianRow[j] = di !== undefined && di > 0 ? 1 : 0;
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
const adjacencyRow = adjacency[i];
|
|
248
|
+
if (!adjacencyRow) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const adjacencyVal = adjacencyRow[j];
|
|
253
|
+
const di = degrees[i];
|
|
254
|
+
if (adjacencyVal !== undefined && adjacencyVal > 0 && di !== undefined && di > 0) {
|
|
255
|
+
const laplacianRow = laplacian[i];
|
|
256
|
+
if (laplacianRow) {
|
|
257
|
+
laplacianRow[j] = -adjacencyVal / di;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return laplacian;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Find k smallest eigenvectors using simplified eigendecomposition
|
|
270
|
+
* This is a simplified implementation - in practice, you'd use LAPACK or similar
|
|
271
|
+
*/
|
|
272
|
+
function findSmallestEigenvectors(matrix: number[][], k: number): {
|
|
273
|
+
eigenvalues: number[];
|
|
274
|
+
eigenvectors: number[][];
|
|
275
|
+
} {
|
|
276
|
+
const n = matrix.length;
|
|
277
|
+
|
|
278
|
+
if (n === 0 || k === 0) {
|
|
279
|
+
return {eigenvalues: [], eigenvectors: []};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// For very small matrices, use the full power iteration approach
|
|
283
|
+
// Remove the simplified approach that was causing issues
|
|
284
|
+
if (k >= n) {
|
|
285
|
+
// If k >= n, we still need proper eigenvectors, not identity
|
|
286
|
+
// Fall through to the power iteration below
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// For spectral clustering, we need proper eigenvectors
|
|
290
|
+
// Special handling for small k values which are common in clustering
|
|
291
|
+
if (k <= 3 && n > k) {
|
|
292
|
+
return computeSmallestEigenvectorsSimple(matrix, k, n);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// For larger k, use power iteration
|
|
296
|
+
const eigenvectors: number[][] = [];
|
|
297
|
+
const eigenvalues: number[] = [];
|
|
298
|
+
const maxIterations = 100;
|
|
299
|
+
|
|
300
|
+
for (let eigIdx = 0; eigIdx < k; eigIdx++) {
|
|
301
|
+
// Initialize random vector
|
|
302
|
+
let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
|
|
303
|
+
|
|
304
|
+
// Normalize initial vector
|
|
305
|
+
const initNorm = Math.sqrt(vector.reduce((sum, val) => sum + (val * val), 0));
|
|
306
|
+
if (initNorm > 0) {
|
|
307
|
+
vector = vector.map((val) => val / initNorm);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Orthogonalize against previous eigenvectors
|
|
311
|
+
for (let j = 0; j < eigIdx; j++) {
|
|
312
|
+
const ejVector = eigenvectors[j];
|
|
313
|
+
if (!ejVector) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const dot = vector.reduce((sum, val, idx) => sum + (val * (ejVector[idx] ?? 0)), 0);
|
|
318
|
+
vector = vector.map((val, idx) => val - (dot * (ejVector[idx] ?? 0)));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Power iteration
|
|
322
|
+
for (let iter = 0; iter < maxIterations; iter++) {
|
|
323
|
+
// Multiply by matrix
|
|
324
|
+
const newVector = Array(n).fill(0) as number[];
|
|
325
|
+
for (let i = 0; i < n; i++) {
|
|
326
|
+
for (let j = 0; j < n; j++) {
|
|
327
|
+
const matrixRow = matrix[i];
|
|
328
|
+
const vecVal = vector[j];
|
|
329
|
+
const matrixVal = matrixRow?.[j];
|
|
330
|
+
if (matrixVal !== undefined && vecVal !== undefined) {
|
|
331
|
+
const nvVal = newVector[i];
|
|
332
|
+
if (nvVal !== undefined) {
|
|
333
|
+
newVector[i] = nvVal + (matrixVal * vecVal);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Orthogonalize against previous eigenvectors
|
|
340
|
+
for (let j = 0; j < eigIdx; j++) {
|
|
341
|
+
const ejVector = eigenvectors[j];
|
|
342
|
+
if (!ejVector) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const dot = newVector.reduce((sum, val, idx) => sum + (val * (ejVector[idx] ?? 0)), 0);
|
|
347
|
+
for (let i = 0; i < n; i++) {
|
|
348
|
+
const ejVal = ejVector[i];
|
|
349
|
+
if (ejVal !== undefined) {
|
|
350
|
+
const nvVal = newVector[i];
|
|
351
|
+
if (nvVal !== undefined) {
|
|
352
|
+
newVector[i] = nvVal - (dot * ejVal);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Normalize
|
|
359
|
+
const norm = Math.sqrt(newVector.reduce((sum, val) => sum + ((val * val)), 0));
|
|
360
|
+
if (norm > 1e-10) {
|
|
361
|
+
vector = newVector.map((val) => val / norm);
|
|
362
|
+
} else {
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Calculate eigenvalue (Rayleigh quotient)
|
|
368
|
+
let eigenvalue = 0;
|
|
369
|
+
const Av = Array(n).fill(0) as number[];
|
|
370
|
+
for (let i = 0; i < n; i++) {
|
|
371
|
+
for (let j = 0; j < n; j++) {
|
|
372
|
+
const matrixRow = matrix[i];
|
|
373
|
+
const vecVal = vector[j];
|
|
374
|
+
const matrixVal = matrixRow?.[j];
|
|
375
|
+
if (matrixVal !== undefined && vecVal !== undefined) {
|
|
376
|
+
const avVal = Av[i];
|
|
377
|
+
if (avVal !== undefined) {
|
|
378
|
+
Av[i] = avVal + (matrixVal * vecVal);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
eigenvalue = vector.reduce((sum, val, idx) => {
|
|
384
|
+
const avVal = Av[idx];
|
|
385
|
+
return sum + (val * (avVal ?? 0));
|
|
386
|
+
}, 0);
|
|
387
|
+
|
|
388
|
+
eigenvectors.push(vector);
|
|
389
|
+
eigenvalues.push(eigenvalue);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return {eigenvalues, eigenvectors};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Normalize matrix rows to unit length
|
|
397
|
+
*/
|
|
398
|
+
function normalizeRows(matrix: number[][]): void {
|
|
399
|
+
for (const row of matrix) {
|
|
400
|
+
const norm = Math.sqrt(row.reduce((sum, val) => sum + (val * val), 0));
|
|
401
|
+
if (norm > 0) {
|
|
402
|
+
for (let j = 0; j < row.length; j++) {
|
|
403
|
+
const val = row[j];
|
|
404
|
+
if (val !== undefined) {
|
|
405
|
+
row[j] = val / norm;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* K-means clustering algorithm
|
|
414
|
+
*/
|
|
415
|
+
function kMeansClustering(
|
|
416
|
+
data: number[][],
|
|
417
|
+
k: number,
|
|
418
|
+
maxIterations: number,
|
|
419
|
+
): {assignments: number[], centroids: number[][]} {
|
|
420
|
+
const n = data.length;
|
|
421
|
+
const d = data[0]?.length ?? 0;
|
|
422
|
+
|
|
423
|
+
// Handle edge cases
|
|
424
|
+
if (n === 0 || k === 0) {
|
|
425
|
+
return {assignments: [], centroids: []};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (k >= n) {
|
|
429
|
+
// Each point is its own cluster
|
|
430
|
+
return {
|
|
431
|
+
assignments: Array.from({length: n}, (_, i) => i),
|
|
432
|
+
centroids: data.slice(0, n),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Initialize centroids by selecting random data points
|
|
437
|
+
const centroids: number[][] = [];
|
|
438
|
+
const selectedIndices = new Set<number>();
|
|
439
|
+
|
|
440
|
+
while (centroids.length < k && selectedIndices.size < n) {
|
|
441
|
+
const idx = Math.floor(Math.random() * n);
|
|
442
|
+
if (!selectedIndices.has(idx) && data[idx]) {
|
|
443
|
+
selectedIndices.add(idx);
|
|
444
|
+
centroids.push([... data[idx]]);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Fill remaining centroids with random values if needed
|
|
449
|
+
while (centroids.length < k) {
|
|
450
|
+
const centroid = Array(d).fill(0) as number[];
|
|
451
|
+
for (let j = 0; j < d; j++) {
|
|
452
|
+
centroid[j] = Math.random() - 0.5;
|
|
453
|
+
}
|
|
454
|
+
centroids.push(centroid);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const assignments = Array(n).fill(0) as number[];
|
|
458
|
+
let oldAssignments = Array(n).fill(-1) as number[];
|
|
459
|
+
|
|
460
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
461
|
+
// Assign points to closest centroids
|
|
462
|
+
for (let i = 0; i < n; i++) {
|
|
463
|
+
let minDistance = Number.POSITIVE_INFINITY;
|
|
464
|
+
let bestCluster = 0;
|
|
465
|
+
|
|
466
|
+
for (let j = 0; j < k; j++) {
|
|
467
|
+
const dataPoint = data[i];
|
|
468
|
+
const centroid = centroids[j];
|
|
469
|
+
if (!dataPoint || !centroid) {
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const distance = euclideanDistance(dataPoint, centroid);
|
|
474
|
+
if (distance < minDistance) {
|
|
475
|
+
minDistance = distance;
|
|
476
|
+
bestCluster = j;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
assignments[i] = bestCluster;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Check for convergence
|
|
484
|
+
let changed = false;
|
|
485
|
+
for (let i = 0; i < n; i++) {
|
|
486
|
+
if (assignments[i] !== oldAssignments[i]) {
|
|
487
|
+
changed = true;
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (!changed) {
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
oldAssignments = [... assignments];
|
|
497
|
+
|
|
498
|
+
// Update centroids
|
|
499
|
+
const counts = Array(k).fill(0) as number[];
|
|
500
|
+
const sums: number[][] = Array.from({length: k}, () => Array(d).fill(0) as number[]);
|
|
501
|
+
|
|
502
|
+
for (let i = 0; i < n; i++) {
|
|
503
|
+
const cluster = assignments[i] ?? 0;
|
|
504
|
+
const dataPoint = data[i];
|
|
505
|
+
if (!dataPoint) {
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const sumsCluster = sums[cluster];
|
|
510
|
+
if (sumsCluster !== undefined) {
|
|
511
|
+
const countVal = counts[cluster];
|
|
512
|
+
if (countVal !== undefined) {
|
|
513
|
+
counts[cluster] = countVal + 1;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
for (let j = 0; j < d; j++) {
|
|
517
|
+
const dpVal = dataPoint[j];
|
|
518
|
+
if (dpVal !== undefined) {
|
|
519
|
+
const sumVal = sumsCluster[j];
|
|
520
|
+
if (sumVal !== undefined) {
|
|
521
|
+
sumsCluster[j] = sumVal + dpVal;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
for (let i = 0; i < k; i++) {
|
|
529
|
+
const countVal = counts[i];
|
|
530
|
+
if (countVal !== undefined && countVal > 0) {
|
|
531
|
+
const sumsRow = sums[i];
|
|
532
|
+
const centroidsRow = centroids[i];
|
|
533
|
+
if (sumsRow !== undefined && centroidsRow !== undefined) {
|
|
534
|
+
for (let j = 0; j < d; j++) {
|
|
535
|
+
const sumVal = sumsRow[j];
|
|
536
|
+
if (sumVal !== undefined) {
|
|
537
|
+
const countVal = counts[i];
|
|
538
|
+
if (countVal !== undefined) {
|
|
539
|
+
centroidsRow[j] = sumVal / countVal;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return {assignments, centroids};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Calculate Euclidean distance between two vectors
|
|
553
|
+
*/
|
|
554
|
+
function euclideanDistance(a: number[], b: number[]): number {
|
|
555
|
+
let sum = 0;
|
|
556
|
+
for (let i = 0; i < a.length; i++) {
|
|
557
|
+
const diff = (a[i] ?? 0) - (b[i] ?? 0);
|
|
558
|
+
sum += diff * diff;
|
|
559
|
+
}
|
|
560
|
+
return Math.sqrt(sum);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Compute smallest eigenvectors for small k (optimized for k=2, k=3)
|
|
565
|
+
*/
|
|
566
|
+
function computeSmallestEigenvectorsSimple(matrix: number[][], k: number, n: number): {
|
|
567
|
+
eigenvalues: number[];
|
|
568
|
+
eigenvectors: number[][];
|
|
569
|
+
} {
|
|
570
|
+
const eigenvectors: number[][] = [];
|
|
571
|
+
const eigenvalues: number[] = [];
|
|
572
|
+
|
|
573
|
+
// First eigenvector is constant (corresponds to eigenvalue 0 for connected graph)
|
|
574
|
+
const firstVector = Array(n).fill(1 / Math.sqrt(n)) as number[];
|
|
575
|
+
eigenvectors.push(firstVector);
|
|
576
|
+
eigenvalues.push(0);
|
|
577
|
+
|
|
578
|
+
// For k >= 2, compute the Fiedler vector (second smallest eigenvector)
|
|
579
|
+
if (k >= 2) {
|
|
580
|
+
// Use power iteration on I - L/lambda_max to find second smallest
|
|
581
|
+
const maxEig = 2; // For normalized Laplacian, max eigenvalue <= 2
|
|
582
|
+
let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
|
|
583
|
+
|
|
584
|
+
// Make orthogonal to first eigenvector
|
|
585
|
+
const dot1 = vector.reduce((sum, val) => sum + (val / Math.sqrt(n)), 0);
|
|
586
|
+
vector = vector.map((val) => val - (dot1 / Math.sqrt(n)));
|
|
587
|
+
|
|
588
|
+
// Power iteration on shifted matrix
|
|
589
|
+
for (let iter = 0; iter < 100; iter++) {
|
|
590
|
+
// Compute (I - L/maxEig) * v
|
|
591
|
+
const newVector = Array(n).fill(0) as number[];
|
|
592
|
+
|
|
593
|
+
// Identity part
|
|
594
|
+
for (let i = 0; i < n; i++) {
|
|
595
|
+
newVector[i] = vector[i] ?? 0;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Subtract L * v / maxEig
|
|
599
|
+
for (let i = 0; i < n; i++) {
|
|
600
|
+
for (let j = 0; j < n; j++) {
|
|
601
|
+
const matrixVal = matrix[i]?.[j] ?? 0;
|
|
602
|
+
const vecVal = vector[j] ?? 0;
|
|
603
|
+
newVector[i] = (newVector[i] ?? 0) - ((matrixVal * vecVal) / maxEig);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Orthogonalize against first eigenvector
|
|
608
|
+
const dot = newVector.reduce((sum, val) => sum + (val / Math.sqrt(n)), 0);
|
|
609
|
+
for (let i = 0; i < n; i++) {
|
|
610
|
+
newVector[i] = (newVector[i] ?? 0) - (dot / Math.sqrt(n));
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Normalize
|
|
614
|
+
const norm = Math.sqrt(newVector.reduce((sum, val) => sum + (val * val), 0));
|
|
615
|
+
if (norm > 1e-10) {
|
|
616
|
+
vector = newVector.map((val) => val / norm);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
eigenvectors.push(vector);
|
|
621
|
+
eigenvalues.push(0.1); // Approximate
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// For k = 3, add another eigenvector
|
|
625
|
+
if (k >= 3) {
|
|
626
|
+
let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
|
|
627
|
+
|
|
628
|
+
// Orthogonalize against previous eigenvectors
|
|
629
|
+
for (const prev of eigenvectors) {
|
|
630
|
+
const dot = vector.reduce((sum, val, idx) => sum + (val * (prev[idx] ?? 0)), 0);
|
|
631
|
+
vector = vector.map((val, idx) => val - (dot * (prev[idx] ?? 0)));
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Similar power iteration
|
|
635
|
+
for (let iter = 0; iter < 50; iter++) {
|
|
636
|
+
const newVector = Array(n).fill(0) as number[];
|
|
637
|
+
|
|
638
|
+
// Identity part
|
|
639
|
+
for (let i = 0; i < n; i++) {
|
|
640
|
+
newVector[i] = vector[i] ?? 0;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Subtract L * v / 2
|
|
644
|
+
for (let i = 0; i < n; i++) {
|
|
645
|
+
for (let j = 0; j < n; j++) {
|
|
646
|
+
const matrixVal = matrix[i]?.[j] ?? 0;
|
|
647
|
+
const vecVal = vector[j] ?? 0;
|
|
648
|
+
newVector[i] = (newVector[i] ?? 0) - ((matrixVal * vecVal) / 2);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Orthogonalize
|
|
653
|
+
for (const prev of eigenvectors) {
|
|
654
|
+
const dot = newVector.reduce((sum, val, idx) => sum + (val * (prev[idx] ?? 0)), 0);
|
|
655
|
+
for (let i = 0; i < n; i++) {
|
|
656
|
+
newVector[i] = (newVector[i] ?? 0) - (dot * (prev[i] ?? 0));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Normalize
|
|
661
|
+
const norm = Math.sqrt(newVector.reduce((sum, val) => sum + (val * val), 0));
|
|
662
|
+
if (norm > 1e-10) {
|
|
663
|
+
vector = newVector.map((val) => val / norm);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
eigenvectors.push(vector);
|
|
668
|
+
eigenvalues.push(0.2); // Approximate
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return {eigenvalues: eigenvalues.slice(0, k), eigenvectors: eigenvectors.slice(0, k)};
|
|
672
|
+
}
|