@graphty/algorithms 1.1.0 → 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.
@@ -0,0 +1,375 @@
1
+ /**
2
+ * TeraHAC - Hierarchical Agglomerative Clustering for Large Graphs
3
+ *
4
+ * This algorithm performs hierarchical clustering on graphs by iteratively
5
+ * merging the closest clusters. Optimized for scalability to handle large
6
+ * graphs efficiently.
7
+ *
8
+ * Based on: "Scaling Hierarchical Agglomerative Clustering to Trillion-Edge Graphs"
9
+ * Google Research 2024
10
+ *
11
+ * @param graph - Input graph to cluster
12
+ * @param config - Configuration options
13
+ * @returns Hierarchical clustering result
14
+ */
15
+ export function teraHAC(graph, config = {}) {
16
+ const { linkage = "average", numClusters, distanceThreshold, maxNodes = 10000, useGraphDistance = true, } = config;
17
+ const nodes = Array.from(graph.nodes());
18
+ const nodeCount = nodes.length;
19
+ if (nodeCount === 0) {
20
+ throw new Error("Cannot cluster empty graph");
21
+ }
22
+ if (nodeCount > maxNodes) {
23
+ console.warn(`Graph has ${String(nodeCount)} nodes, which exceeds maxNodes (${String(maxNodes)}). Performance may be degraded.`);
24
+ }
25
+ // Initialize each node as its own cluster
26
+ const clusters = new Map();
27
+ let nextClusterId = nodeCount;
28
+ for (let i = 0; i < nodeCount; i++) {
29
+ const node = nodes[i];
30
+ if (!node) {
31
+ continue;
32
+ }
33
+ const clusterId = i.toString();
34
+ clusters.set(clusterId, {
35
+ id: clusterId,
36
+ members: new Set([node.id]),
37
+ distance: 0,
38
+ size: 1,
39
+ });
40
+ }
41
+ // Calculate initial distance matrix
42
+ const distanceMatrix = calculateDistanceMatrix(graph, nodes, useGraphDistance);
43
+ const mergeDistances = [];
44
+ // Priority queue for efficient nearest neighbor finding
45
+ const mergeCandidates = initializeMergeCandidates(clusters, distanceMatrix);
46
+ let dendrogram;
47
+ // Perform agglomerative clustering
48
+ while (clusters.size > 1) {
49
+ // Find closest pair of clusters
50
+ const { cluster1Id, cluster2Id, distance } = findClosestPair(mergeCandidates);
51
+ // Check stopping criteria
52
+ if (numClusters && clusters.size <= numClusters) {
53
+ break;
54
+ }
55
+ if (distanceThreshold && distance > distanceThreshold) {
56
+ break;
57
+ }
58
+ // Get the two clusters to merge
59
+ const cluster1 = clusters.get(cluster1Id);
60
+ const cluster2 = clusters.get(cluster2Id);
61
+ if (!cluster1 || !cluster2) {
62
+ continue;
63
+ }
64
+ // Create new merged cluster
65
+ const newClusterId = (nextClusterId++).toString();
66
+ const mergedMembers = new Set([...cluster1.members, ...cluster2.members]);
67
+ const newCluster = {
68
+ id: newClusterId,
69
+ members: mergedMembers,
70
+ left: cluster1,
71
+ right: cluster2,
72
+ distance,
73
+ size: cluster1.size + cluster2.size,
74
+ };
75
+ // Remove old clusters
76
+ clusters.delete(cluster1Id);
77
+ clusters.delete(cluster2Id);
78
+ // Add new cluster
79
+ clusters.set(newClusterId, newCluster);
80
+ mergeDistances.push(distance);
81
+ // Update merge candidates
82
+ updateMergeCandidates(mergeCandidates, cluster1Id, cluster2Id, newClusterId, clusters, distanceMatrix, linkage);
83
+ dendrogram = newCluster;
84
+ }
85
+ // If we have multiple remaining clusters, create a virtual root
86
+ if (clusters.size > 1) {
87
+ const remainingClusters = Array.from(clusters.values());
88
+ let root = remainingClusters[0];
89
+ if (!root) {
90
+ dendrogram = undefined;
91
+ }
92
+ else {
93
+ for (let i = 1; i < remainingClusters.length; i++) {
94
+ const currentCluster = remainingClusters[i];
95
+ if (!currentCluster) {
96
+ continue;
97
+ }
98
+ const newRoot = {
99
+ id: (nextClusterId++).toString(),
100
+ members: new Set([...root.members, ...currentCluster.members]),
101
+ left: root,
102
+ right: currentCluster,
103
+ distance: Infinity,
104
+ size: root.size + currentCluster.size,
105
+ };
106
+ root = newRoot;
107
+ }
108
+ dendrogram = root;
109
+ }
110
+ dendrogram = root;
111
+ }
112
+ dendrogram ?? (dendrogram = Array.from(clusters.values())[0]);
113
+ // Extract flat clustering
114
+ const finalNumClusters = numClusters ?? clusters.size;
115
+ const flatClusters = dendrogram ? extractFlatClustering(dendrogram, finalNumClusters) : new Map();
116
+ if (!dendrogram) {
117
+ throw new Error("Failed to create dendrogram");
118
+ }
119
+ return {
120
+ dendrogram,
121
+ clusters: flatClusters,
122
+ distances: mergeDistances,
123
+ numClusters: finalNumClusters,
124
+ };
125
+ }
126
+ /**
127
+ * Calculate distance matrix between all pairs of nodes
128
+ */
129
+ function calculateDistanceMatrix(graph, nodes, useGraphDistance) {
130
+ const n = nodes.length;
131
+ const matrix = Array.from({ length: n }, () => new Array(n).fill(Infinity));
132
+ if (useGraphDistance) {
133
+ // Use graph-based distances (shortest path)
134
+ for (let i = 0; i < n; i++) {
135
+ const node = nodes[i];
136
+ if (!node) {
137
+ continue;
138
+ }
139
+ const distances = bfsShortestPaths(graph, node.id);
140
+ for (let j = 0; j < n; j++) {
141
+ if (i !== j) {
142
+ const targetNode = nodes[j];
143
+ if (targetNode && i < matrix.length && j < n) {
144
+ const row = matrix[i];
145
+ if (row && j < row.length) {
146
+ const distance = distances.get(targetNode.id);
147
+ row[j] = distance ?? Infinity;
148
+ }
149
+ }
150
+ }
151
+ else if (i < matrix.length) {
152
+ const row = matrix[i];
153
+ if (row && j < row.length) {
154
+ row[j] = 0;
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
160
+ else {
161
+ // Use simple edge-based distances
162
+ for (let i = 0; i < n; i++) {
163
+ for (let j = i + 1; j < n; j++) {
164
+ const node1 = nodes[i];
165
+ const node2 = nodes[j];
166
+ if (node1 && node2) {
167
+ const hasEdge = graph.hasEdge(node1.id, node2.id);
168
+ const distance = hasEdge ? 1 : 2; // Connected: 1, not connected: 2
169
+ if (i < matrix.length) {
170
+ const rowI = matrix[i];
171
+ if (rowI && j < rowI.length) {
172
+ rowI[j] = distance;
173
+ }
174
+ }
175
+ if (j < matrix.length) {
176
+ const rowJ = matrix[j];
177
+ if (rowJ && i < rowJ.length) {
178
+ rowJ[i] = distance;
179
+ }
180
+ }
181
+ }
182
+ }
183
+ if (i < matrix.length) {
184
+ const row = matrix[i];
185
+ if (row && i < row.length) {
186
+ row[i] = 0;
187
+ }
188
+ }
189
+ }
190
+ }
191
+ return matrix;
192
+ }
193
+ /**
194
+ * BFS-based shortest path calculation from a source node
195
+ */
196
+ function bfsShortestPaths(graph, source) {
197
+ const distances = new Map();
198
+ const queue = [[source, 0]];
199
+ const visited = new Set();
200
+ visited.add(source);
201
+ distances.set(source, 0);
202
+ while (queue.length > 0) {
203
+ const item = queue.shift();
204
+ if (!item) {
205
+ break;
206
+ }
207
+ const [current, distance] = item;
208
+ for (const neighbor of graph.neighbors(current)) {
209
+ if (!visited.has(neighbor)) {
210
+ visited.add(neighbor);
211
+ distances.set(neighbor, distance + 1);
212
+ queue.push([neighbor, distance + 1]);
213
+ }
214
+ }
215
+ }
216
+ return distances;
217
+ }
218
+ /**
219
+ * Initialize merge candidates priority queue
220
+ */
221
+ function initializeMergeCandidates(clusters, distanceMatrix) {
222
+ const candidates = [];
223
+ const clusterIds = Array.from(clusters.keys());
224
+ for (let i = 0; i < clusterIds.length; i++) {
225
+ for (let j = i + 1; j < clusterIds.length; j++) {
226
+ const id1 = clusterIds[i];
227
+ const id2 = clusterIds[j];
228
+ if (!id1 || !id2) {
229
+ continue;
230
+ }
231
+ const row = distanceMatrix[parseInt(id1)];
232
+ if (!row) {
233
+ continue;
234
+ }
235
+ const distance = row[parseInt(id2)] ?? Infinity;
236
+ // Include all candidates, even disconnected ones (with finite but large distance)
237
+ candidates.push({
238
+ cluster1Id: id1,
239
+ cluster2Id: id2,
240
+ distance: distance === Infinity ? 100 : distance,
241
+ });
242
+ }
243
+ }
244
+ // Sort by distance (ascending)
245
+ candidates.sort((a, b) => a.distance - b.distance);
246
+ return candidates;
247
+ }
248
+ /**
249
+ * Find the closest pair of clusters
250
+ */
251
+ function findClosestPair(mergeCandidates) {
252
+ // Return the first (closest) valid candidate
253
+ const candidate = mergeCandidates.shift();
254
+ if (!candidate) {
255
+ throw new Error("No merge candidates available");
256
+ }
257
+ return candidate;
258
+ }
259
+ /**
260
+ * Update merge candidates after a merge operation
261
+ */
262
+ function updateMergeCandidates(mergeCandidates, oldCluster1Id, oldCluster2Id, newClusterId, clusters, distanceMatrix, linkage) {
263
+ // Remove candidates involving the merged clusters
264
+ for (let i = mergeCandidates.length - 1; i >= 0; i--) {
265
+ const candidate = mergeCandidates[i];
266
+ if (!candidate) {
267
+ continue;
268
+ }
269
+ if (candidate.cluster1Id === oldCluster1Id || candidate.cluster1Id === oldCluster2Id ||
270
+ candidate.cluster2Id === oldCluster1Id || candidate.cluster2Id === oldCluster2Id) {
271
+ mergeCandidates.splice(i, 1);
272
+ }
273
+ }
274
+ // Add new candidates for the merged cluster
275
+ const newCluster = clusters.get(newClusterId);
276
+ if (!newCluster) {
277
+ return;
278
+ }
279
+ for (const [clusterId, cluster] of clusters) {
280
+ if (clusterId !== newClusterId) {
281
+ const distance = calculateClusterDistance(newCluster, cluster, distanceMatrix, linkage);
282
+ mergeCandidates.push({
283
+ cluster1Id: newClusterId,
284
+ cluster2Id: clusterId,
285
+ distance: distance === Infinity ? 100 : distance,
286
+ });
287
+ }
288
+ }
289
+ // Re-sort candidates
290
+ mergeCandidates.sort((a, b) => a.distance - b.distance);
291
+ }
292
+ /**
293
+ * Calculate distance between two clusters based on linkage criterion
294
+ */
295
+ function calculateClusterDistance(cluster1, cluster2, distanceMatrix, linkage) {
296
+ const members1 = Array.from(cluster1.members);
297
+ const members2 = Array.from(cluster2.members);
298
+ const distances = [];
299
+ // Calculate all pairwise distances between cluster members
300
+ for (const member1 of members1) {
301
+ for (const member2 of members2) {
302
+ const idx1 = parseInt(member1.toString());
303
+ const idx2 = parseInt(member2.toString());
304
+ if (idx1 < distanceMatrix.length && idx2 < distanceMatrix.length) {
305
+ const row = distanceMatrix[idx1];
306
+ if (row) {
307
+ const distance = row[idx2];
308
+ if (distance !== undefined) {
309
+ distances.push(distance);
310
+ }
311
+ }
312
+ }
313
+ }
314
+ }
315
+ if (distances.length === 0) {
316
+ return Infinity;
317
+ }
318
+ // Apply linkage criterion
319
+ switch (linkage) {
320
+ case "single":
321
+ return Math.min(...distances);
322
+ case "complete":
323
+ return Math.max(...distances);
324
+ case "average":
325
+ return distances.reduce((sum, d) => sum + d, 0) / distances.length;
326
+ case "ward":
327
+ // Simplified Ward linkage (would need cluster centroids for full implementation)
328
+ return distances.reduce((sum, d) => sum + (d * d), 0) / distances.length;
329
+ default:
330
+ return distances.reduce((sum, d) => sum + d, 0) / distances.length;
331
+ }
332
+ }
333
+ /**
334
+ * Extract flat clustering from dendrogram
335
+ */
336
+ function extractFlatClustering(dendrogram, numClusters) {
337
+ const clusters = new Map();
338
+ if (numClusters === 1) {
339
+ // Single cluster
340
+ const clusterId = 0;
341
+ for (const member of dendrogram.members) {
342
+ clusters.set(member, clusterId);
343
+ }
344
+ return clusters;
345
+ }
346
+ // Find clusters at the specified level
347
+ const clusterNodes = [];
348
+ const queue = [dendrogram];
349
+ while (queue.length > 0 && clusterNodes.length < numClusters) {
350
+ const current = queue.shift();
351
+ if (!current) {
352
+ break;
353
+ }
354
+ if (!current.left || !current.right || clusterNodes.length + queue.length + 1 >= numClusters) {
355
+ // This is a leaf or we need to keep this level
356
+ clusterNodes.push(current);
357
+ }
358
+ else {
359
+ // Continue decomposing
360
+ queue.push(current.left, current.right);
361
+ }
362
+ }
363
+ // Assign cluster IDs
364
+ for (let i = 0; i < clusterNodes.length; i++) {
365
+ const cluster = clusterNodes[i];
366
+ if (!cluster) {
367
+ continue;
368
+ }
369
+ for (const member of cluster.members) {
370
+ clusters.set(member, i);
371
+ }
372
+ }
373
+ return clusters;
374
+ }
375
+ //# sourceMappingURL=terahac.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terahac.js","sourceRoot":"","sources":["../../../src/research/terahac.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphty/algorithms",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Graph algorithms library for browser environments implemented in TypeScript",
5
5
  "author": "Adam Powers <apowers@ato.ms>",
6
6
  "main": "dist/algorithms.js",
@@ -35,10 +35,18 @@
35
35
  "typecheck": "tsc --noEmit",
36
36
  "prepare": "husky",
37
37
  "build": "tsc",
38
+ "build:bundle": "node scripts/build-bundle.js",
39
+ "build:all": "npm run build && npm run build:bundle",
40
+ "build:gh-pages": "npm run build:bundle && node scripts/build-gh-pages.js",
38
41
  "build:watch": "tsc --watch",
39
42
  "dev": "tsc --watch",
43
+ "examples": "node examples/run-all-examples.js",
44
+ "examples:run": "node examples/run-all-examples.js",
45
+ "examples:html": "npm run build:bundle && vite",
46
+ "serve": "npm run build:bundle && vite",
40
47
  "commit": "cz",
41
- "ready:commit": "npm run build && npm run lint && npm run test:all"
48
+ "ready:commit": "npm run build && npm run lint && npm run test:all",
49
+ "watch": "tsc --watch"
42
50
  },
43
51
  "repository": {
44
52
  "type": "git",
@@ -97,6 +105,7 @@
97
105
  "tsx": "^4.20.3",
98
106
  "typescript": "^5.8.3",
99
107
  "typescript-eslint": "^8.34.1",
108
+ "vite": "^7.0.5",
100
109
  "vitest": "^3.2.4"
101
110
  }
102
111
  }
@@ -59,13 +59,27 @@ export function spectralClustering(
59
59
  // Find k smallest eigenvectors
60
60
  const eigenResult = findSmallestEigenvectors(laplacianMatrix, k);
61
61
 
62
- // Normalize the eigenvector matrix row-wise (for normalized spectral clustering)
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)
63
78
  if (laplacianType === "normalized") {
64
- normalizeRows(eigenResult.eigenvectors);
79
+ normalizeRows(dataPoints);
65
80
  }
66
81
 
67
- // Perform k-means clustering on the eigenvectors
68
- const kmeans = kMeansClustering(eigenResult.eigenvectors, k, maxIterations);
82
+ const kmeans = kMeansClustering(dataPoints, k, maxIterations);
69
83
 
70
84
  // Build communities
71
85
  const communities: NodeId[][] = Array.from({length: k}, () => []);
@@ -265,32 +279,34 @@ function findSmallestEigenvectors(matrix: number[][], k: number): {
265
279
  return {eigenvalues: [], eigenvectors: []};
266
280
  }
267
281
 
268
- // For small matrices or when k >= n, use a simplified approach
269
- if (n <= 3 || k >= n) {
270
- // Create identity-like eigenvectors for simplicity
271
- const eigenvectors: number[][] = [];
272
- const eigenvalues: number[] = [];
273
-
274
- for (let i = 0; i < Math.min(k, n); i++) {
275
- const vector = Array(n).fill(0) as number[];
276
- vector[i] = 1;
277
- eigenvectors.push(vector);
278
- eigenvalues.push(i);
279
- }
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
+ }
280
288
 
281
- return {eigenvalues, eigenvectors};
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);
282
293
  }
283
294
 
284
- // For larger matrices, use power iteration to find dominant eigenvectors
285
- // This is still simplified but better than random
295
+ // For larger k, use power iteration
286
296
  const eigenvectors: number[][] = [];
287
297
  const eigenvalues: number[] = [];
288
- const maxIterations = 50;
298
+ const maxIterations = 100;
289
299
 
290
300
  for (let eigIdx = 0; eigIdx < k; eigIdx++) {
291
301
  // Initialize random vector
292
302
  let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
293
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
+
294
310
  // Orthogonalize against previous eigenvectors
295
311
  for (let j = 0; j < eigIdx; j++) {
296
312
  const ejVector = eigenvectors[j];
@@ -543,3 +559,114 @@ function euclideanDistance(a: number[], b: number[]): number {
543
559
  }
544
560
  return Math.sqrt(sum);
545
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
+ }
package/src/index.ts CHANGED
@@ -33,5 +33,8 @@ export type {
33
33
  // Algorithm exports
34
34
  export * from "./algorithms/index.js";
35
35
 
36
+ // Research algorithms exports (Priority 4)
37
+ export * from "./research/index.js";
38
+
36
39
  // Data structure exports
37
40
  export * from "./data-structures/index.js";